phoenixray2000
728dbb72df
feat(export): 支持 CSV 计划导出和稳定导出索引 ( #114 )
...
两个核心增强:
1. **CSV 计划导出工作流** (`--write-plan-csv` / `--from-plan-csv`)
- 支持 blacklist (export=0 跳过) / whitelist (export=1 导出) 两种模式
- `--size-mode estimate|scan` 控制是否扫本地附件
- UTF-8 BOM 编码, Excel/WPS 直接打开
- 解决了之前"动不动全量导出"的痛点
2. **`_export_index.json` 稳定导出索引**
- 用 username 追踪当前 JSON 文件
- 联系人备注/群名变化时自动重命名旧文件 (而不是产生孤儿)
- 同名联系人冲突时自动追加 `__<username>` 后缀
- atomic write (tmp + os.replace), bootstrap from existing files
3. **JSON metadata 扩展**
- 新增 `date_first_msg / date_last_msg / contact_remark / contact_nick_name / contact_tags / contact_memo`
测试: 717 行, 20 tests pass, 覆盖 CRUD 索引、黑白名单、命名冲突、incremental rename、UTF-8 BOM。
2026-05-19 02:20:30 +08:00
ylytdeng
e5e2269947
fix: PR #107 后续清理 (security/正确性/一致性)
...
针对 4 个 review agent 在 PR #107 (5649 行巨型 PR) 找到的关键问题做最小
侵入修复。已合并代码本身能跑,这次是收紧 security + 消重 + 文档一致性。
## 安全修复
### wxwork_keys.json 落盘权限 (find_wxwork_keys.py)
含明文 16-byte raw key 的 keys 文件,之前 default umask 落盘。改成:
1. 写 tmp 文件
2. chmod 0o600 (Unix 严格 owner-only; Windows 上 chmod 控制只读位,
至少避免世界可读最差情况)
3. atomic rename
旧产物自然过期,新生成的都受保护。
### SNS XXE 防护 (export_sns.py)
朋友圈 XML 来源是不可信输入(他人发的 content),原 `ET.fromstring()`
完全没过滤,可被恶意 entity expansion / 外部实体引用攻击。加跟
`mcp_server._XML_UNSAFE_RE` 同模式的过滤(拒 `<!DOCTYPE>` / `<!ENTITY>`)
+ 200KB 大小上限。`_parse_timeline_xml` 检查后才进 ET.fromstring。
## 正确性 / 消重
### AES 对齐公式统一 (decode_image.py + decrypt_sns.py + export_sns.py)
原本三处各写一份:
- decode_image.py: aes_size -= ~(~aes_size % 16) ← bitwise trick
- decrypt_sns.py: 同上
- export_sns.py: aes_size + (16 - aes_size%16) if … else aes_size+16
两个公式数学等价(对 0/1/15/16/17/100/1000/12345 全部验证一致),但
bitwise trick 难读且漂移风险高。抽 `aligned_aes_block_size()` 到
decode_image.py 作 canonical 实现, 另两处 import 复用。
### 32-bit pointer 假设明确化 (find_wxwork_keys.py)
reviewer 担心 `_read_u32` 在 64-bit 进程上错位,实测 WXWork.exe 5.0.x
是 **32-bit 进程** (`Program Files (x86)\WXWork\` + PE Machine = x86),
所以 4 字节读指针是对的。加注释明确这个假设,腾讯如果升级到 64-bit
要重做整套逆向, 当前实测全部 17 db 解密通过印证。
## 一致性
### main.py show_status() 走 _config_file_path() (main.py)
原硬编码 `config_file = "config.json"` 绕开 PR #107 新引入的
`_config_file_path()`,打包成 exe 后 cwd 不一定是 exe 目录,会读到错
位置。改成 `from config import _config_file_path`。
### EXE_USAGE.md 输出目录写错 (EXE_USAGE.md)
EXE_USAGE 说导出到 `export/`,代码实际 `output_base_dir = wechat_files/
<wxid>/`,联系人下还是 `messages.csv/html/json` 而不是
`message_0.db.csv`。修正成真实结构。
## 文档
README 加两段:
- 安全提示: keys 文件 chmod 0600 + 不要 commit 到 git
- 朋友圈 XML XXE 防护说明
## 测试
185/185 通过 (含已有 wxsqlite3 roundtrip + image v2 + msg types filter
+ pagination hint + chat export helpers 等)。
aligned_aes_block_size 单独验证跟旧公式等价(0/1/15/16/17/100/1000/12345)。
## 未跟进 (后续 follow-up issue)
- 3 处 V1/V2/XOR 解密代码完全重复(decode_image / decrypt_sns /
export_messages 各自实现)——抽出来工作量大,本次先抽 helper 不动
完整解密路径,后续单独 PR
- export_messages HTML base64 内联图片可能爆几 GB,应改成可选 flag
- SNS / wxwork export / batch_decrypt_images / voice_to_mp3 测试缺位
(0 个 test)
2026-05-17 17:00:20 +08:00
xincheng
26ecaac11e
fix argparse
2026-05-17 07:12:26 +08:00
Davy
3801f69d71
feat(main): add export, all, status subcommands
2026-05-14 15:43:56 +08:00
Belugary
403f014ac0
feat: 新增 decode-images 子命令(批量解密 .dat 图片到明文图片树)
...
## 问题
\`decode_image.py\` 目前只有 \`decrypt_dat_file()\` 单文件 API,以及 \`monitor_web\` 在收到新消息时\"按需解一张\"的路径。**没有\"一次性扫 attach 目录、产出明文图片树到固定路径\"的批量入口**。结果是任何想把微信图片做下游消费(数据分析、搜索索引、归档、第三方 viewer)的用户都得各自写一遍 walk + decrypt 的 wrapper,且各自约定输出布局,生态不收敛。
## 修复
- \`decode_image.py\` 新增 \`decode_all_dats(attach_dir, out_dir, aes_key, xor_key, force, on_file)\` 函数,扫描 \`<attach_dir>/<chat_hash>/<YYYY-MM>/Img/*.dat\` 并镜像产出 \`<out_dir>/<chat_hash>/<YYYY-MM>/<file_md5>.<ext>\`。
- \`main.py\` 新增 \`decode-images\` 子命令(早路由,跳过 \`check_wechat_running\` 和 \`ensure_keys\` —— 这条路径只读 \`.dat\` 文件,既不需要微信进程也不需要 DB 密钥)。
设计选择:
- **输出布局 1:1 镜像 attach**,只做最小 path massage(去 \`Img/\`、去 \`_t/_h\` 缩略图后缀、换扩展名),不发明新结构。下游能用 \`md5(username)\` 反推路径,无需读 mapping 文件。
- **幂等性 = 按 basename 存在性 skip**,不做 mtime 比较 —— \`.dat\` 是 content-hash 命名(\`file_md5 = 文件内容 md5\`),实际上 write-once。\`--force\` 强制重解。
- **原子写**:解密先写 \`<basename>.<ext>.tmp\`(同目录),\`os.replace\` 到正式路径。中断不留半个 jpg。残留 \`.tmp\` 不会被 skip 误判(glob 显式排除)。
- **错误隔离**:单文件失败计入 \`failed\` 继续下一个,stderr 打 \`[WARN]\` 指出相对路径。退出码 2 表示\"部分失败,产物部分可用\"。
- **V2 无 key**:计入 \`skipped_no_key\` 而非 \`failed\` —— 这是可恢复状态(跑 \`find_image_key_macos.py\` / \`find_image_key.py\` 后重跑即可),跟\"真失败\"区分对待。V1 / 老 XOR 不依赖 \`image_aes_key\`。
- **wxgf 容器**只产 \`.hevc\` 裸流,**不**做 mp4 转换:上游不引入 ffmpeg subprocess 依赖,转换是消费层职责。
- **CLI override**:\`--attach-dir\` / \`--decoded-dir\` / \`--aes-key\` / \`--xor-key\` / \`--force\` 都可覆盖 \`config.json\`,适合 CI / 多账号 / 容器化场景。
## 测试
新文件 \`tests/test_decode_images_batch.py\`,13 个新测试:
- \`PathParsingTests\` (4):glob 命中 / \`_t\` 后缀剥离 / \`_h\` 后缀剥离 / chat_hash + YYYY-MM 镜像
- \`IdempotentTests\` (3):已存在跳过 / \`--force\` 覆写 / 残留 \`.tmp\` 不误判
- \`AtomicWriteTests\` (3):成功路径无 \`.tmp\` / decrypt 返回 None 无 \`.tmp\` / decrypt 抛异常无 \`.tmp\`
- \`V2NoKeyTests\` (2):V2 + 无 key → skipped_no_key / V1 + 无 key 仍解码
- \`CallbackTests\` (1):\`on_file\` 回调每文件触发
基线 183 → 196 通过(+13 新增),0 回归。\`decrypt_dat_file\` 用 mock 隔离(避免依赖真实加密图片);\`is_v2_format\` 走真实 magic 检测路径。
## 范围
- \`decode_image.py\`:新增 \`decode_all_dats\` 函数,134 行,纯加,不改任何现有 API。
- \`main.py\`:新增 \`_run_decode_images\` helper + 早路由 + 用法 hint,104 行加 2 行删。无 backward-compat 影响。
- \`tests/test_decode_images_batch.py\`:新增,295 行。合成 fixture(假 V1/V2 magic + mock decrypt_dat_file),不依赖真实加密素材。
2026-05-14 15:39:13 +08:00
btc-z
02bc9c1840
feat: 新增语音 MCP 工具 + macOS 密钥提取修复 ( #53 )
...
* feat: 新增语音 MCP 工具 + macOS 密钥提取修复
- 新增 get_voice_messages / decode_voice / transcribe_voice MCP 工具
- 语音数据存储在 media_0.db VoiceInfo 表(SILK v3 格式)
- decode_voice 解码为 WAV 文件(saved to decoded_voices/)
- transcribe_voice 通过 Whisper 自动识别语言转录
- 新增 get_chat_history oldest_first 参数,支持从最早消息开始分页
- 修复 macOS 下 check_wechat_running / ensure_keys 逻辑
- 改用 pgrep 检测微信进程,绕过不支持 macOS 的 Python 扫描器
- 无 all_keys.json 时打印清晰引导,提示运行 C 版扫描器
- 新增 Makefile(build / keys / decrypt / web 快捷命令)
- .gitignore 补充 find_all_keys_macos 二进制和 decoded_voices/
* fix: 语音查询支持多分片 media DB + 文件名唯一化
解决 PR #53 review 的阻塞项 #1,顺手修 #3、#6。
#1 `_get_media_db_path()` 硬编码 `media_0.db`
- 新增模块级 `MEDIA_DB_KEYS`,镜像 `MSG_DB_KEYS` 的分片发现逻辑
- `_fetch_voice_row` 遍历所有分片,按 `(chat_name_id, local_id)`
首个命中即返回;单条语音在 media DB 家族内唯一,命中即可停
- `get_voice_messages` 从每个分片各取 `LIMIT limit`,合并排序后
截断到 `limit`。选择"每分片取 limit 条再合并"而非"按
max(create_time) 排序后逐个取到 limit 即停止":后者假设分片
间时间不重叠,一旦 WeChat 改分片策略就会静默丢消息;前者工作
量 O(N 分片 × limit),在任何分片布局下都正确
#3 输出文件名冲突
- `_silk_to_wav` 增加 `local_id` 参数,输出 `{user}_{time}_{lid}.wav`,
同一秒内两条语音不会互相覆盖;两个调用方都已在作用域内持有
`local_id`
#6 `_fetch_voice_row` 的 `local_id=None` 死分支
- 随 #1 的重写一并删除,`local_id` 改为必填位置参数
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com >
* refactor: macOS 密钥提取分层下沉到 find_all_keys.py
解决 PR #53 review 的阻塞项 #2。
review 里提到"跟 PR #51 冲突"实测不存在 —— PR #51 当前 0 文件改动
(fork 分支已与上游同步),但架构建议本身是对的:macOS 处理应集中
在 `find_all_keys.py`,而不是在 `main.py` 提前 return 截胡。
- `main.py:ensure_keys()` 移除 darwin 专属提前返回分支,macOS 走
和其他平台相同的 `extract_keys()` 路径
- `find_all_keys.py:_load_impl()` 在 darwin 分支抛出带
`sudo ./find_all_keys_macos` 操作指引的 RuntimeError;非 macOS
的平台兜底分支保留
- `main.py` 里已有 `except RuntimeError` 会打印并 `sys.exit(1)`,
用户可见行为不变
未来若有 PR 在 `find_all_keys.py` 加 macOS 自动编译 / dispatch,
直接替换这段 RuntimeError 即可,不再需要改 `main.py`。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com >
* chore: Makefile 支持 PYTHON 变量覆盖
解决 PR #53 review 的非阻塞项 #7。
原 Makefile 硬编码 `.venv/bin/python3`,没有 venv 的用户跑 `make
decrypt` 直接报错。引入 `PYTHON ?= .venv/bin/python3`:默认行为
不变(仍走 venv),想用系统 Python 的用户 `PYTHON=python3 make
decrypt` 即可。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com >
* docs: 回应 PR #53 review #4 — 澄清 silk-python 与 pysilk 包名关系
验证:本项目 import 的 `pysilk` 实际由 `pip install silk-python`
(synodriver/pysilk) 提供;pypi 上另有同名 `pysilk==0.0.1` 是无内容
的占位包,不可用。错误消息里 `pip install silk-python` 已经是对的,
但 reader 看到 `import pysilk` 仍会困惑,所以:
- `_silk_to_wav` 的 import 处加一行注释,点名所用的是
synodriver 版本,并提醒 pypi 上还有 pilk / pysilk 两个同类包
- `decode_voice` / `transcribe_voice` 的 docstring 加 "依赖:" 行,
明确 "pip install silk-python (import 名为 pysilk)",MCP 客户端
读 tool 描述就能看到正确的安装命令
未新增 requirements.txt 条目:voice 支持是可选功能(tool 内
try/except ImportError 懒加载),保持非必需依赖的语义。
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com >
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com >
2026-04-23 14:10:22 +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
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
PeanutSplash
fd4a2fce31
fix(config): handle corrupted config file and improve encoding detection
2026-03-03 22:49:03 +08:00
PeanutSplash
6898a065d7
feat: add unified entry point and multi-process key extraction
...
Add main.py as single entry point that auto-detects config, extracts keys, and launches Web UI or decrypts databases in one command.
Refactor find_all_keys to scan all Weixin.exe processes instead of only the largest one, enabling multi=account support.
2026-03-03 22:20:12 +08:00