# wechat-cli 项目

## 项目概述
基于本地微信数据库的 CLI 查询工具，支持会话、消息、联系人、群成员等查询。AI-first 设计，输出结构化 JSON。

## 技术栈
- Python 3.10+, Click, pycryptodome, zstandard
- SQLCipher 4 解密（AES-256-CBC）
- npm 分发 + PyInstaller 打包

## 目录结构
- `wechat_cli/` — 主 CLI 包（commands/, core/, keys/, output/）
- `skills/` — Claude Code skills（export-chat, tencent-cos-upload）
- `collector/` — 群聊消息自动采集器
- `collect_chats.py` — 采集器入口脚本
- `npm/` — npm 分发包

## 群聊采集器（collector/）
自动采集微信群聊消息的子系统：
- `collector/config.py` — 配置管理（MySQL、COS、扫描策略、回溯补录）
- `collector/storage.py` — MySQL 存储层（wechat_group_message 表）
- `collector/wechat_adapter.py` — 封装 wechat_cli.core 的适配层，包含 XML 元信息提取
- `collector/scanner.py` — 优先级队列扫描调度 + COS 媒体上传
- `collector/backfill.py` — 媒体回溯补录（watchdog 实时监听 + 定时兜底扫描）

## 关键约定
- wechat-cli 是只读工具，不修改微信数据
- 群聊通过 `@chatroom` 后缀识别
- 消息可跨多个 `message_N.db` 文件
- DBCache 使用 mtime 机制缓存解密副本到 `/tmp/wechat_cli_cache/`
- 采集器独立维护状态，不干扰 `~/.wechat-cli/last_check.json`
- 非文本消息（文件/视频/链接等）从 XML 中提取元信息存入 content 字段，不依赖本地文件下载
- 群聊消息 content_raw 格式为 `wxid:\n<xml>`，解析 XML 时需先剥离发送者前缀
- 聊天记录合并转发（app_type=19）解析 recorditem XML，格式化为多行纯文本
- 引用/回复消息（app_type=57）提取 refermsg 中的 svrid 建立消息关联，content 格式化为 "正文\n  ↳ 回复 发送者: 被引用内容"
- 数据库通过 `svr_msg_id`（微信 server_id）和 `refer_msg_svrid`（refermsg/svrid）实现引用消息的关联查询
- 采集器扫描策略: hot=5s, warm=10s, cold 退避至 60s（backoff 1.2），保证 ≤1min 入库
- 媒体文件只采集原始可读文件，不做 .dat 解密
  - 图片: 从 `temp/RWTemp/YYYY-MM/{md5}.ext` 获取（微信查看图片后自动生成）
  - 文件: 从 `msg/file/YYYY-MM/filename` 获取
  - 视频: 从 `msg/video/YYYY-MM/{rawmd5}.mp4` 获取，或从 `temp/RWTemp/YYYY-MM/{rawmd5}.mp4` 获取（直接发送的视频点击预览后缓存于此）
- 图片消息 content 格式为 `[图片] {md5} size:{length}`，md5 来自消息 XML 的 md5 属性，length 为 XML 的 length 属性（= RWTemp 中文件的精确字节数）
- **重要**: XML md5 属性并非文件内容的 md5（可能是加密前或服务端 hash），文件名也是派生 hash。匹配图片的可靠方式是通过 XML length 与文件大小精确比对，md5 仅作为兼容旧记录的回退
- 视频消息 content 格式为 `[视频] {playlength}秒 rawmd5:{rawmd5} size:{rawlength}`，rawmd5 是本地 mp4 文件名，rawlength 是文件字节数。匹配优先 rawmd5（文件名精确匹配），回退 rawlength 与文件大小比对
- **注意**: 直接发送的视频 rawmd5 和 rawlength 为空/0，此时使用 XML 的 `length` 属性作为 size（即缓存到 RWTemp 中文件的精确字节数），content 为 `[视频] {playlength}秒 size:{length}`
- 语音消息 content 格式为 `[语音] {N}秒 size:{length}`，voicelength 为语音时长（ms→s四舍五入），length 为语音数据字节数。匹配通过文件大小精确比对
- 文件消息 content 格式为 `{filename} ({human_size}) size:{exact_bytes}`，匹配优先文件名+精确size双重校验，回退纯文件名子串匹配
- 回溯补录: PollingObserver（5s轮询）监听 temp/RWTemp + msg/file + msg/video 目录，匹配失败 30s 后自动重试；每 1 分钟定时扫描 media_url=NULL 记录（7天内）兜底
- 默认日志级别 DEBUG，可通过配置文件 `~/.wechat-cli/collect-chats/config.json` 修改
