Commit Graph

22 Commits

Author SHA1 Message Date
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