Files
zWorkFlow/export_chat.py
Davy 67de4a1d0c feat: 批量导出所有聊天为 JSON + 提取共享 helper 模块 (#77)
* Add export_all_chats.py: batch export all WeChat chats to JSON

Mirrors export_chat.py functionality to export every chat in the
decrypted WeChat database at once. Output format is byte-for-byte
identical to export_chat.py.

Usage: python3 export_all_chats.py [output_dir]

- Reads all sessions from decrypted/session/session.db
- Uses the same content extraction pipeline as export_chat.py:
  _resolve_sender, _extract_content, _msg_type_str, sticker/video/system
- Outputs group_<display>.json or single_<display>.json with same schema
- Progress reporting every 100 exports with summary at end

* refactor: 将 7 个重复 helper 函数提取到 chat_export_helpers.py

根据 PR #77 review 反馈,将 export_chat.py 和 export_all_chats.py 中
逐字复制的消息格式化函数提取到共享模块 chat_export_helpers.py。

提取的函数:
  MSG_TYPE_MAP, _msg_type_str, _resolve_sender,
  _decode_sticker_desc, _format_sticker_message,
  _format_system_message, _format_video_message, _extract_content

两个导出脚本现在从 chat_export_helpers import 所需函数,
消除代码漂移风险。
2026-05-12 11:42:48 +08:00

125 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
将单个聊天的全部消息导出为 JSON。
用法:
.venv/bin/python3 export_chat.py <chat_name> [output.json]
参数:
<chat_name> 联系人显示名、备注名、群名或 wxid。
[output.json] 可选输出路径,默认 "<chat_name>_export.json"
示例:
.venv/bin/python3 export_chat.py <contact_name>
.venv/bin/python3 export_chat.py <group_name> /tmp/out.json
输出 JSON 的紧凑结构:
{
"chat": "<display name>",
"username": "<wxid 或 @chatroom>",
"exported_at": "YYYY-MM-DD HH:MM:SS",
"is_group": true, // 仅群聊出现
"messages": [
{"local_id": 1, "timestamp": 1713..., "sender": "me", "content": "..."},
{"local_id": 2, "timestamp": 1713..., "sender": "<name>", "type": "voice"}
]
}
默认值/空值会被省略: text 消息省略 "type",无可提取内容时省略 "content"
1-on-1 聊天省略 "is_group"
语音消息以 type "voice" 导出且不带 transcription 字段;运行
transcribe_chat.py 可用 Whisper 补齐转录。
需先完成 WeChat DB 解密(详见 README
完整 schema、字段语义与加载示例: docs/chat_export_format.md
"""
import json
import sqlite3
import sys
from contextlib import closing
from datetime import datetime
import mcp_server
from chat_export_helpers import (
_extract_content,
_msg_type_str,
_resolve_sender,
)
def export_chat(chat_name, output_path):
ctx = mcp_server._resolve_chat_context(chat_name)
if ctx is None:
print(f"Could not resolve chat: {chat_name}")
sys.exit(1)
username = ctx["username"]
display_name = ctx["display_name"]
# resolve_username 对模糊匹配会静默选第一个命中,打印一下便于用户核对。
print(f"Resolved to: {display_name} ({username})")
if not ctx["message_tables"]:
print(f"No message tables found for {username}")
sys.exit(1)
names = mcp_server.get_contact_names()
# Each shard has its own Name2Id table, so we must pair rows with the
# id_to_username map from their source DB.
all_rows = []
for table_info in ctx["message_tables"]:
db_path = table_info["db_path"]
table_name = table_info["table_name"]
with closing(sqlite3.connect(db_path)) as conn:
id_to_username = mcp_server._load_name2id_maps(conn)
rows = mcp_server._query_messages(conn, table_name, limit=None, oldest_first=True)
for row in rows:
all_rows.append((row, id_to_username))
# Sort across shards by create_time (defensive "or 0" in case a row has NULL).
all_rows.sort(key=lambda pair: pair[0][2] or 0)
messages = []
for row, id_to_username in all_rows:
local_id, local_type, create_time, real_sender_id, content, ct = row
sender = _resolve_sender(row, ctx, names, id_to_username)
type_str = _msg_type_str(local_type)
rendered = _extract_content(local_id, local_type, content, ct, username, display_name)
# Compact format: omit defaults/nulls. type defaults to "text", transcription
# is added later by transcribe_chat.py only for voice messages. See CLAUDE.md.
msg = {
"local_id": local_id,
"timestamp": create_time,
"sender": sender,
}
if type_str != "text":
msg["type"] = type_str
if rendered is not None:
msg["content"] = rendered
messages.append(msg)
output = {
"chat": display_name,
"username": username,
"exported_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
"messages": messages,
}
if ctx["is_group"]:
output["is_group"] = True
with open(output_path, "w", encoding="utf-8") as f:
json.dump(output, f, ensure_ascii=False, indent=2)
print(f"Exported {len(messages)} messages to {output_path}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python3 export_chat.py <chat_name> [output.json]")
sys.exit(1)
chat = sys.argv[1]
out = sys.argv[2] if len(sys.argv) > 2 else f"{chat}_export.json"
export_chat(chat, out)