Commit Graph

25 Commits

Author SHA1 Message Date
ylytdeng
cf0e67b50b feat(monitor_web): 加终止按钮 + voice_to_mp3 优雅降级
## 痛点

用户反馈:
1. 没终止按钮 - 任务一旦点了, 跑死(✗ 失败)或跑长(解密 30s+)都没法停
2. ⑧ 语音转 MP3 报 ModuleNotFoundError: 'pilk', 直接 traceback

## Patch 1: 终止任务

### Backend
- _tool_running 加 proc / cancelled 字段
- _run_tool_task 把 proc 暴露到 _tool_running, 每轮 stdout read 后
  检查 cancelled 标志, 命中就 break
- 新加 POST /api/tool/cancel 路由: proc.terminate() + 1.5s 后 kill,
  设 cancelled=True 让 tool runner 收尾
- tool_done 事件加 cancelled 字段

### Frontend
- 任务运行时, 触发按钮临时变成 "🛑 终止" + 红色脉冲动画
- 再点一下就调 /api/tool/cancel
- tool_done 收到后还原按钮文本和颜色
- "⊘ 已终止" 状态徽章替代 "✓ 完成"

### CSS
- .tool-task-btn.cancel: 实心红渐变 + box-shadow + pulseRed 1.5s 动画

## Patch 2: voice_to_mp3 优雅降级

之前直接 `import pilk` 失败时打 traceback, 用户看不懂。改成:

  try: import pilk
  except ImportError:
      print 友好提示 + pip install pilk 命令 + 退出

加 ffmpeg 在 PATH 检查 (pilk 解码后需要 ffmpeg 编 MP3), 给三平台
安装指引。

requirements.txt 加注释说明 pilk 还需要 ffmpeg 配合, pyinstaller
仅打包需要(开发不必装)。

## 测试

185/185 通过 (不影响现有功能)。

实测 web UI:
- 点 ⑧ 语音转 MP3 → 立刻看到友好提示而不是 traceback
- 点任何任务 → 按钮变红色"🛑 终止" → 再点 → 任务收尾 → 状态变"⊘ 已终止"

## 仍未跟进 (follow-up)

- 导出筛选: ③ 导出全部聊天 / ⑦ 企微导出 一点就跑全量很可怕。
  应该弹模态框选会话 + 格式. 单独开 issue #112 跟进, 需要:
  - 新加 GET /api/sessions 路由列会话
  - export_all_chats / export_wxwork_messages 加 --filter 参数
  - 前端模态框 UI
2026-05-17 18:14:28 +08:00
ylytdeng
fb260063ef style(monitor_web): Web UI polish - 4 类 design 改进
跟 design-critique skill 评审后, 实施 4 项可执行改进:

## 1. 🔴 修 header flex overflow bug

原: .stats { margin-left:auto } 在浏览器窄时占主轴空间, 把后面的
🛠️ 工具 + ⚙️ 按钮挤出视野 (用户截图反复出现"看不到按钮")。

改:
  .stats: 加 min-width:0 + overflow:hidden + white-space:nowrap
  .header: 加 flex-wrap:wrap + row-gap:8px 兜底换行
  .tools-btn / .settings-btn: 加 flex-shrink:0 不许被压缩

## 2. 加 design tokens (:root)

20+ 处硬编码颜色 / 间距 / 圆角散落, 改一处怕漏一处。统一:
  颜色: --bg / --bg-elev / --surface / --border / --accent / --warn ...
  间距: --s1..s6 (4/8/12/16/24/32)
  字号: --t1..t6 (11/12/13/15/18/24)
  圆角: --r1 (6) / --r2 (10) / --r3 (14) / --r-pill (999)
  阴影: --shadow-1 / --shadow-2 / --shadow-glow

工具区 CSS 全量切到 token, 老消息流 CSS 保持原样不动 (避免回归)。

## 3. 视觉强化 (tab + primary button)

Tab:
  - active 加 linear-gradient 渐变背景 (透明 → accent-bg)
  - ::after 蓝色下划线 + box-shadow glow 发光
  - hover 微微抬亮
  - 切换有 fadeIn 200ms 动画

Primary 按钮 (Step 1 主操作):
  - 改成实心渐变 #4fc3f7 → #29b6f6 (之前是空心边框, 跟普通按钮区别太小)
  - 加 box-shadow 立体感 + inset 高光
  - hover 上浮 2px + 阴影放大
  - 跟普通按钮 (透明 surface) 主次关系清晰

前置条件:
  - 之前: 黄色左边框 + 灰文字 (像 form error)
  - 现在: 紧凑 pill (背景 + 圆角 999), 头部加 ● 点
  - 显眼但不焦虑

## 4. 上台阶的小招

  body: radial-gradient(ellipse at top, #14142a, #0a0a0f) 顶部柔光
  body: 中文字体优先 PingFang SC / HarmonyOS / Source Han Sans
        (Segoe UI 渲染中文糊)
  body: antialiased + optimizeLegibility
  header: backdrop-filter blur(20px) + position:sticky 玻璃质感
  日志框: JetBrains Mono > SF Mono > Consolas, line-height 1.55

## 默认展开 toolsPanel

之前默认隐藏要先点 🛠️ 工具 按钮才能用, 用户进来一脸懵。改成默认展开:
toolsPanel 加 class="show"。toggleTools() 仍能切换 (隐藏后可还原)。

## 实测

headless chrome 截图确认:
- 🟢 三个按钮全可见
- 🟢 tab active 蓝色高亮 + 发光下划线
- 🟢 STEP 1 主按钮 primary 蓝色实心 突出
- 🟢 STEP 2 普通按钮 透明 layer
- 🟢 前置条件 pill 黄色 chip
- 🟢 背景 radial gradient 顶部柔光过渡

测试 185/185 通过。

## 跟评审建议的对照

| critique 建议 | 实施 |
|---|---|
| 修 header flex bug |  |
| 加 design tokens 系统化 |  (工具区全量, 老消息流保持原状) |
| tab active 渐变 + 发光下划线 |  |
| primary 按钮实心渐变 + 阴影 |  |
| body radial-gradient 背景 |  |
| 中文字体栈优先苹方 |  |
| header backdrop-filter |  |
| 前置条件 pill 化 |  |
2026-05-17 18:09:58 +08:00
ylytdeng
62b015337a refactor(monitor_web): Web UI 工具箱按产品分 tab,不再混杂个人/企微/工具
## 反馈

用户实测后说"产品没设计好,企微和个人微信应该分开"。原版把 8 个按钮平
铺成两行,虽有"个人微信:" / "朋友圈/企微:" label 但只是文字注释,视觉
分组弱,工作流顺序也不明确 (新用户不知道该先点哪个再点哪个)。

## 重设计 (3 个 tab + 工作流 Step 分组)

```
[📱 个人微信]  [🏢 企业微信]  [🔧 工具]   <- tab 切换

📱 个人微信 (active)
⚠️ 前置: 微信 PC 版正在运行且已登录
─────────────────────────────────────────
STEP 1 — 解密
  [① 提取密钥 + 解密数据库] (primary)
  [② 提取图片密钥]

STEP 2 — 导出/解码 (可独立运行,需先 Step 1)
  [③ 导出全部聊天 (JSON)]
  [④ 批量解密 .dat 图片]
  [⑤ 朋友圈解密 + 导出]

[实时日志区]
```

```
🏢 企业微信
⚠️ 前置: 企业微信 PC 版正在运行且已登录 (独立于个人微信)
─────────────────────────────────────────
STEP 1 — 解密
  [① 提取密钥 + 解密数据库] (primary)

STEP 2 — 导出
  [② 导出聊天 (CSV/HTML/JSON)]

[实时日志区]
```

```
🔧 工具
💡 跟微信/企微进程无关,只读已解密产物
─────────────────────────────────────────
语音 / 转码
  [语音转 MP3 (需 ffmpeg in PATH)]

[实时日志区]
```

## 改动细节

### CSS
- 加 `.tool-tabs` / `.tool-tab` / `.tool-pane` (tab 头 + 内容切换)
- 加 `.tool-prereq` (黄色边框前置条件提示)
- 加 `.tool-step` + `.tool-step-label` (大写小灰字步骤标签)
- 加 `.primary` 给 Step 1 主操作按钮更显眼的边框

### JS
- `switchToolTab(name)` 切换 tab,记录到 `window.__activeToolPane`
- `runTool` 把日志写到当前激活 pane 的 `toolLog_<pane>` 而不是单一全局
- SSE `tool_log` 监听同样按 `__activeToolPane` dispatch 到对应日志框
- DOMContentLoaded 时同时绑 tab click + 任务 button click

### Backend
- TOOL_TASKS 不动 (still 8 个),只是前端重新组织
- 一次只跑一个任务的约束 (_tool_lock) 不变

## 优势

- 工作流明确: 新用户进来直接看到"前置 + Step 1 → Step 2"
- 视觉分组: tab 切换比平铺一目了然
- 跟现有 SSE / settings panel 不冲突
- 浏览器 hard refresh 即可看到新版

Tests: 185/185 通过
2026-05-17 17:50:08 +08:00
ylytdeng
bf5d16d48c feat(monitor_web): 加 Web UI 工具箱 - 替代 tkinter app_gui.py 的 8 个按钮
把 PR #107 引入的 tkinter GUI (app_gui.py, 929 行) 整套功能搬到现有
monitor_web 的浏览器 UI 上。复用已有的 SSE 通道,后端跑子进程,实时把
stdout 推到浏览器。

## 实现

### Backend (~110 行)
- `TOOL_TASKS` dict 定义 8 个任务 (cmd 串列表)
- `_run_tool_task(job_id, task_name)` 后台线程顺序跑 steps,
  subprocess.Popen + readline → broadcast_sse 推 event=tool_log
- 完成后推 event=tool_done
- `Handler.do_POST` 加 /api/tool 路由,接 {"task":"..."} 触发
- _tool_lock 保证全局同时只跑 1 个任务,避免两个解密挤内存
- 子进程环境传 WECHAT_DECRYPT_NONINTERACTIVE=1, 走脚本里已有的非交互
  分支 (自动选最近账号等)
- subprocess 用 CREATE_NO_WINDOW 隐藏黑窗 (Win)

### Frontend (~130 行 HTML/CSS/JS)
- header 加 🛠️ 工具 按钮
- toggleTools() 切换 #toolsPanel 显隐 (默认隐藏)
- 8 个按钮分两行: 个人微信 (① 解密 / ② 图片密钥 / ③ 导出聊天 / ④ 批量
  解图片) + 朋友圈/企微 (⑤ 朋友圈 / ⑥ 企微解密 / ⑦ 企微导出 / ⑧ 语音
  转 MP3)
- 黑色日志区,Monaco/Consolas 字体,实时 append (auto-scroll 到底)
- 状态条  运行中 / ✓ 完成 / ✗ 失败
- es.addEventListener('tool_log' / 'tool_done') 跟现有 message 事件
  共存

### 跟现有监听共存
- 不动 messages 容器和消息流逻辑
- 跟 settings 面板共用一套 header 按钮风格
- SSE 通道复用 /stream, 不另起 channel

## 实测

WXWork 在跑的情况下点 ⑥ 企微解密, 浏览器实时显示:
- find_wxwork_keys cipher 结构体扫描进度
- enc_key=cc68fa7d...4c (16 字节)
- decrypt_wxwork_db 17/17 db 解密成功
- 完成后 wxwork_keys.json / wxwork_decrypted/*.db mtime 全部更新

测试 185/185 通过 (不影响现有消息监听逻辑)。

## 对比 app_gui.py

| | tkinter (app_gui.py) | Web UI (monitor_web) |
|---|---|---|
| 外观 | 90 年代 Windows | 暗色现代渐变 |
| 中文字体 | 渲染糊 | 浏览器原生清晰 |
| 跨平台 | 名义支持实际只 Win | macOS/Linux/Win 浏览器都行 |
| 远程访问 |  | bind 0.0.0.0 即可 |
| 维护 | 单独 929 行 stdlib tkinter | 跟监听共享 SSE/HTML |
| 包大小 (打包后) | 多带 tkinter dll | 走 monitor_web 已有的 stdlib |

## 后续

仍开着 (留 follow-up):
- 导出 ③ / ⑦ 当前是"全量"模式; tkinter 里弹了"选会话"对话框,
  Web 版要做 /api/sessions + 模态框 (估 0.5 天)
- 任务完成后给"打开输出目录"按钮 (15 分钟)
- 删 app_gui.py + 改 build.bat 把 monitor_web 当 exe 入口 (1 小时)
- 数据浏览 Tab (浏览器看解密后的 db / 朋友圈 timeline) - P3
2026-05-17 17:36:12 +08:00
Belugary
1aa12c86fa fix(monitor_web): use full message content, not truncated session summary (#42)
## Problem

In `--web` mode, messages longer than ~90 chars get truncated in the
SSE feed. `SessionMonitor.check_updates()` pushes `SessionTable.summary`
to clients, but `summary` is WeChat's own ~80-char preview kept for the
client-side chat list — not the full message body.

## Fix

`_lookup_latest_local_id` already hits `Msg_<md5(username)>` for the
row at `(username, create_time)` to obtain `local_id` for #79's
dedup. Extend the same query to also return `message_content` and
`WCDB_CT_message_content`, and use it to replace `summary` when the
DB body is longer:

    SELECT local_id, message_content, WCDB_CT_message_content
    FROM [Msg_<md5>]
    WHERE create_time = ?
    ORDER BY local_id DESC LIMIT 1

Same row, same query — zero additional IO vs. the prior `MAX(local_id)`.

Renamed to `_lookup_latest_message` to reflect the new
`(local_id, content)` return shape. zstd handling and `wxid_xxx:\n`
group-prefix stripping mirror the existing `SessionTable.summary`
logic in `check_updates`, so the SSE `content` field stays in the same
format clients already render.

Replacement is conservative — only swaps in `full_content` when
strictly longer than `summary`. This never shortens existing behavior
and degrades cleanly if the message-DB write hasn't landed yet (the
SessionTable-vs-message_N.db timing race that #79 already documented).

## Scope

- `monitor_web.py`: one helper extended + one call site adjusted. No
  schema change, no new dependency, no client/UI change.
- `_check_hidden_messages` cold path is untouched — its same-second
  multi-message coverage still runs as before.
2026-05-17 16:44:48 +08:00
ylytdeng
eb544b2bd6 refactor: monitor_web 复用 _extract_transfer_info 避免双份维护
PR #85 把转账消息解析放在了 mcp_server._extract_transfer_info(处理
snake/camel 字段名漂移 + 未知 paysubtype 兜底),但 monitor_web.py
内联重新实现了一遍 paysubtype 标签表 + camelCase fallback。

后果:将来 WeChat 新增 paysubtype 时需要两处改,容易漂。

修复:monitor_web 改为调 mcp_server._extract_transfer_info,跟
chat_export_helpers._extract_transfer_extras 走同一条路径。

UI 行为零变化:
- 已知 paysubtype 显示中文 label(同原行为)
- 未知 paysubtype 显示空串(避免"未知(paysubtype=N)"在 UI 出现)
- 字段抽取/截断逻辑不变

本地验证 OLD vs NEW 字节级一致。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:11:19 +08:00
Belugary
f03df51561 feat: parse WeChat transfer messages (appmsg type=2000) (#85)
Add structured parsing for transfer messages so they no longer fall
through to the generic `[链接/文件]` fallback in chat history exports.
Mirrors the dispatch + helper pattern PR #65 (merged-forward type=19)
established for `base_type=49` appmsg sub-types.

## What is added

**Helpers (mcp_server.py):**
- `_TRANSFER_PAYSUBTYPE_LABEL` — maps the 6 community-consensus paysubtypes
  (1 发起 / 3 已收款 / 4 已退还 / 5 过期已退还 / 7 待领取 / 8 已领取);
  unknown values degrade to `未知(paysubtype=N)` so a new variant in a
  future WeChat build is visible rather than silently dropped.
- `_extract_transfer_info(appmsg)` — pulls fields out of `<wcpayinfo>`,
  with snake/camelCase fallback (`feedesc`/`feeDesc`, `pay_memo`/`paymemo`)
  observed across WeChat versions.
- `_format_transfer_message_text(appmsg, title)` — one-line render
  for chat history: `[转账·已收款] ¥100.00 备注: lunch`.

**Dispatch (mcp_server.py):**
- `_format_app_message_text` gains an `app_type == 2000` branch that
  routes to `_format_transfer_message_text`. `get_chat_history`,
  `export_chat`, `export_all_chats` and `monitor_web` all inherit
  automatically.

**New MCP tool (mcp_server.py):**
- `decode_transfer(chat_name, local_id, create_time=0)` — full
  structured view: direction, amount, memo, payer/receiver wxid,
  transfer id, transcation id, begin/invalid timestamps. Uses the
  same multi-shard scan + ambiguity-by-create_time pattern as
  `decode_file_message` / `decode_record_item`.

**CLI wrapper:**
- `decode_transfer.py` at the repo root — argparse wrapper that prints
  the same text as the MCP tool, returning non-zero exit when the
  message can't be decoded (script-friendly).

**JSON export (chat_export_helpers.py + export_chat.py + export_all_chats.py):**
- `_extract_content` now returns `(rendered, extras)`. `extras` carries
  structured fields when a message type has more signal than the
  human-readable string (currently: transfers → `type:"transfer" +
  transfer:{direction, fee_desc, pay_memo, ...}`). The channel is
  forward-compatible — future additions (video号 metadata, expanded
  merged-forward, etc.) flow through the same shape without changing
  the caller signature. JSON consumers that only read `content` are
  unaffected; the change is additive.

**monitor_web (monitor_web.py):**
- Backend dispatch branch + orange-yellow `.msg-transfer` card CSS +
  `renderRich` JS handler.

## Tests

12 new cases in `tests/test_record_decoders.py`:

- `TransferPaysubTypeLabelTests` — locks the 6-value label table.
- `ExtractTransferInfoTests` (6 cases) — full field round-trip, missing
  `<wcpayinfo>` fallback, snake/camelCase variants, unknown paysubtype
  degradation, empty paysubtype handling.
- `FormatTransferMessageTextTests` (4 cases) — initiate / received-with-memo /
  missing-wcpayinfo / missing-fee-desc.
- `AppMessageDispatchTransferTests` — `_format_app_message_text` routes
  type=2000 correctly so `get_chat_history` / `export_chat` both pick
  it up.

All fixtures use synthetic placeholder values (`wxid_payer_synth`,
`¥100.00`, `1` + 27×`0`); no real PII or transaction IDs.

## Scope

7 files, +546 / -15 (additions only — no behavior change for existing
message types). All 180 tests pass locally (168 baseline + 12 new).
2026-05-12 21:03:08 +08:00
ylytdeng
c45c107f45 fix: 密集消息遗漏(issue #79)— 去重 key 加 local_id
根因:_shown_keys 之前用 (username, timestamp, msg_type) 当 key,导致
同秒同类型多条消息(如"逐条转发"10 条文字)的去重 key 完全相同。
SessionTable 触发 emit 第一条后把 key 加进 _shown_keys,
_check_hidden_messages 查到剩余 N-1 条时全部命中"已显示",全部跳过。
juneleung 实测 "10 丢 4"。

本地用解密后的 message_message_0.db 验证:
  - 真实数据存在 4 条同秒消息(local_id 72480..72483)
  - 旧逻辑:1/4 收到
  - 新逻辑:4/4 收到

改动:

1. _shown_keys 改用 (username, local_id) 精确去重
2. 新增 _lookup_latest_local_id(username, timestamp) — SessionTable 触发
   推送时查 message_N.db 拿对应 local_id
3. _check_hidden_messages 的 SQL 加 local_id 字段,过滤循环用 local_id
4. _shown_keys 清理逻辑改为按数量上限(local_id 不能按时间 prune)

时机风险:SessionTable 写入比 message DB 早几毫秒,_lookup_latest_local_id
可能查不到 → 返回 None,跳过加 key。_check_hidden_messages 1 秒后查到
该消息时自己加 key,结果是偶发轻微重复(比丢消息好)。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 13:39:56 +08:00
ylytdeng
15cfdcd4cc fix: 改名/改备注/改群名时联系人缓存不刷新(issue #67)
之前 commit e86e00d 的修复只覆盖「新增联系人不在缓存」的场景:

    if username not in self.contact_names:
        refresh()

改名/改备注/改群名时 username 一直在缓存里,永远跳过刷新,
导致显示老名字。

改成检测 contact.db mtime 变化触发全量 reload,受 30 秒 cooldown
节流(避免微信高频写 contact.db 时 CPU 抖动)。三种变更场景统一覆盖:

- 新增联系人(原 #46 / e86e00d 场景)
- 修改备注名(issue #67)
- 修改群名

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 20:54:18 +08:00
Belugary
ec921dd897 fix: monitor_web 用 webbrowser.open 替代 cmd.exe 实现跨平台开浏览器 (#70)
之前 main() 启动 HTTP server 后用 `os.system('cmd.exe /c start <url>')`
自动开浏览器, 这条命令在非 Windows 平台 cmd.exe 不存在, os.system 返回
非零退出码 (不抛异常, 外层 except 抓不到), 调用静默失败 → 自动开浏览器
功能在 Linux / macOS 完全失效; 同时 shell 会把 `cmd.exe: command not found`
写到终端 stderr 干扰用户.

改用 Python 标准库 webbrowser.open(), 跨平台自动选默认浏览器, 无新增依赖.
2026-05-05 22:47:39 +08:00
ylytdeng
e86e00df87 fix: 新联系人/新群名称不刷新(issue #46)
之前的修复 load_contact_names() 读的是 decrypted/contact/contact.db
静态快照,新加联系人不在里面,所以"自动刷新"实际不生效。

现改为通过 db_cache 实时解密源 contact.db 再加载,确保新增联系人
即时可见。db_cache 内部靠 mtime 检测变化,微信写入后下次查询会触发
重新解密。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-23 13:55:51 +08:00
ylytdeng
69a2f44240 feat: /api/history 支持按群过滤和增量拉取,更新 README API 文档
- /api/history 新增 chat、since、limit 参数
- README 新增 HTTP API 端点说明和联系人标签工具文档

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 11:43:41 +08:00
ylytdeng
7eb29b03e8 feat: 新增联系人标签查询功能
解析 contact.db 的 contact_label 表和 extra_buffer protobuf Field #30,
支持查询标签列表及指定标签下的成员。

- mcp_server.py: 新增 get_contact_tags / get_tag_members MCP 工具
- monitor_web.py: 新增 /api/tags JSON 端点,支持 ?name= 过滤

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 09:54:21 +08:00
ylytdeng
b80e7d1c14 fix: 新群/新联系人自动刷新联系人缓存
检测到消息的用户名不在联系人缓存中时,自动重新加载
contact.db,解决新建群聊一直显示 chatroom ID 的问题。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 18:43:34 +08:00
ylytdeng
944546beb1 fix: 统一所有 JSON 文件读写为 UTF-8 编码
Windows 中文环境默认编码为 GBK,未指定 encoding 会导致
config.json/all_keys.json 解析失败。修复 9 个文件共 17 处。

Closes #32

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 14:32:37 +08:00
ylytdeng
7020409543 fix: full_decrypt 写入前自动创建输出目录
full_decrypt 打开 out_path 写入时未创建父目录,
首次运行 monitor_web 且 decrypted/ 不存在时会报
FileNotFoundError。

Fixes #22

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 17:21:11 +08:00
ylytdeng
030680eb85 fix: 修复短时间大量消息丢失问题
旧逻辑用 `if ts == prev_ts: continue` 粗暴跳过上轮时间戳的所有消息,
但同一秒内可能有多条不同消息(如连续转发公众号文章),导致只显示
最后一条,其余丢失。

改为用 (username, timestamp, msg_type) 精确去重:
- 主消息和 hidden 消息显示后都记录到 _shown_keys
- 过滤时精确匹配已显示的消息,不再按时间戳整体跳过
- _shown_keys 每轮清理过期条目(保留 5 分钟),防止内存泄漏

Fixes #20

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 19:52:46 +08:00
PeanutSplash
6d9b2c0fe4 refactor(find_all_keys): extract shared key scan logic 2026-03-07 21:35:24 +08:00
PeanutSplash
f9c338b48d feat: add Linux support with cross-platform memory scanning
- Add Linux memory scanner (`find_all_keys_linux.py`) using `/proc/<pid>/mem`,
  same approach as Windows/macOS — no GDB, no function offsets, no restart needed
- Extract Windows-specific code to `find_all_keys_windows.py`
- Make `find_all_keys.py` a platform dispatcher (Windows / Linux)
- Add `key_utils.py` for cross-platform path matching (`/` vs `\` in all_keys.json)
- Update `config.py` with Linux auto-detection of db_storage paths
- Update all consumers (decrypt_db, monitor, monitor_web, mcp_server) to use
  `get_key_info()` for platform-agnostic key lookup

Tested on remote Linux container: 15/15 DBs scanned, decrypted, and verified.
2026-03-07 21:35:24 +08:00
ylytdeng
2b03a81a8f fix: 统一路径分隔符为正斜杠,修复 macOS/Linux 兼容性
all_keys.json 中的 key 统一使用 `/` 作为路径分隔符,
消除 Windows 反斜杠硬编码,确保跨平台兼容。

涉及文件: find_all_keys.py, decrypt_db.py, monitor.py,
monitor_web.py, mcp_server.py, decode_image.py, latency_test.py

Fixes #17

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 00:53:48 +08:00
ylytdeng
3b4b811cc3 fix: 清理调试代码 + 提升服务稳定性
- 移除 _debug_log、signal handler、atexit 等调试代码
- 添加 allow_reuse_address 防止重启端口占用
- warmup 线程加外层异常捕获防止静默崩溃
- 恢复启动自动打开浏览器

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 20:41:36 +08:00
ylytdeng
121fa9f7bd feat: 自动检测WeChat路径 + 通知过滤规则
- config.py: 自动从 %APPDATA% ini 读取数据盘符,扫描 xwechat_files
  找到 db_storage 路径,多账号时交互选择,首次运行免手动配置
- monitor_web.py: 右侧设置面板支持自定义通知规则(群名/发送人模糊
  匹配),命中时触发浏览器通知 + 蜂鸣声 + 金色高亮,规则存 localStorage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 22:58:30 +08:00
ylytdeng
c85367ff08 feat: 富媒体内容解析、表情包显示、组合消息修复
- 表情包内联显示: emoticon.db CDN映射 + 下载缓存
- 富媒体内容: 链接卡片/文件/视频号/小程序/引用/位置等完整渲染
- 修复文字+图片组合消息丢失 (前端去重key加消息类型)
- 新增隐藏消息检测: 异步查message DB找回同秒内其他消息
- MonitorDBCache线程安全: per-key锁防并发解密损坏
- Web UI优化: 气泡样式/群聊发送者/图片点击放大

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 11:55:11 +08:00
ylytdeng
da7525db95 Add image decryption and inline preview for WeChat V2 format
Support all three .dat encryption formats:
- Old XOR format: single-byte XOR, auto-detect key from magic bytes
- V1 format: AES-ECB with fixed key (md5("0")[:16]) + XOR tail
- V2 format (2025-08+): AES-128-ECB + raw middle + XOR tail

New files:
- decode_image.py: unified image decryption module (XOR/V1/V2)
- find_image_key.py: extract AES key from WeChat process memory
- find_image_key_monitor.py: continuous monitoring version for key capture

monitor_web.py changes:
- Inline image preview in Web UI with async decryption
- MonitorDBCache for mtime-based DB decryption caching
- username-to-DB mapping for image resolution chain
- /img/ endpoint for serving decoded images
- SSE image_update events for real-time preview updates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 00:30:01 +08:00
joshua-deng
4c91eb34ef WeChat 4.0 database decryptor and real-time message monitor
Extract encryption keys from Weixin.exe process memory, decrypt all
SQLCipher 4 databases, and monitor new messages via Web UI with ~100ms latency.
2026-02-28 12:03:38 +08:00