feat(main): add export, all, status subcommands
This commit is contained in:
160
main.py
160
main.py
@@ -1,16 +1,21 @@
|
|||||||
"""
|
"""
|
||||||
WeChat Decrypt 一键启动
|
WeChat Decrypt 一键启动
|
||||||
|
|
||||||
python main.py # 提取密钥 + 启动 Web UI
|
python main.py # 提取密钥 + 启动 Web UI
|
||||||
python main.py decrypt # 提取密钥 + 解密全部数据库
|
python main.py decrypt # 提取密钥 + 解密全部数据库
|
||||||
|
python main.py export # 提取密钥 + 解密 + 批量导出聊天记录
|
||||||
|
python main.py all # 从零到完成:密钥 → 解密 → 导出
|
||||||
|
python main.py status # 显示当前数据状态
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import glob
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import sys
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
import functools
|
|
||||||
print = functools.partial(print, flush=True)
|
print = functools.partial(print, flush=True)
|
||||||
|
|
||||||
from key_utils import strip_key_metadata
|
from key_utils import strip_key_metadata
|
||||||
@@ -127,7 +132,6 @@ def ensure_keys(keys_file, db_dir):
|
|||||||
keys = json.load(f)
|
keys = json.load(f)
|
||||||
except (json.JSONDecodeError, ValueError):
|
except (json.JSONDecodeError, ValueError):
|
||||||
keys = {}
|
keys = {}
|
||||||
# 检查密钥是否匹配当前 db_dir(防止切换账号后误复用旧密钥)
|
|
||||||
saved_dir = keys.pop("_db_dir", None)
|
saved_dir = keys.pop("_db_dir", None)
|
||||||
if saved_dir and os.path.normcase(os.path.normpath(saved_dir)) != os.path.normcase(os.path.normpath(db_dir)):
|
if saved_dir and os.path.normcase(os.path.normpath(saved_dir)) != os.path.normcase(os.path.normpath(db_dir)):
|
||||||
print(f"[!] 密钥文件对应的目录已变更,需要重新提取")
|
print(f"[!] 密钥文件对应的目录已变更,需要重新提取")
|
||||||
@@ -149,7 +153,6 @@ def ensure_keys(keys_file, db_dir):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# 提取后再次检查
|
|
||||||
if not os.path.exists(keys_file):
|
if not os.path.exists(keys_file):
|
||||||
print("[!] 密钥提取失败")
|
print("[!] 密钥提取失败")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -165,13 +168,116 @@ def ensure_keys(keys_file, db_dir):
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
def show_status():
|
||||||
|
"""显示当前数据状态"""
|
||||||
|
cfg = {}
|
||||||
|
config_file = "config.json"
|
||||||
|
if os.path.exists(config_file):
|
||||||
|
with open(config_file, encoding="utf-8") as f:
|
||||||
|
cfg = json.load(f)
|
||||||
|
print(f"[config] db_dir = {cfg.get('db_dir', '?')}")
|
||||||
|
else:
|
||||||
|
print("[config] 未找到 config.json")
|
||||||
|
|
||||||
|
keys_files = sorted(glob.glob("all_keys*.json"))
|
||||||
|
print(f"[keys] {len(keys_files)} 个密钥文件")
|
||||||
|
for kf in keys_files:
|
||||||
|
sz = os.path.getsize(kf) / 1024
|
||||||
|
print(f" {kf} ({sz:.0f} KB)")
|
||||||
|
|
||||||
|
decrypted_dir = cfg.get("decrypted_dir", "decrypted")
|
||||||
|
if os.path.exists(decrypted_dir):
|
||||||
|
dbs = glob.glob(os.path.join(decrypted_dir, "**/*.db"), recursive=True)
|
||||||
|
total_mb = sum(os.path.getsize(f) for f in dbs) / 1024 / 1024
|
||||||
|
print(f"[decrypt] {len(dbs)} 个数据库 ({total_mb:.0f} MB)")
|
||||||
|
# 检查是否有消息内容(约略估计是否已导出)
|
||||||
|
for db in dbs:
|
||||||
|
if "message" in os.path.basename(db):
|
||||||
|
sz = os.path.getsize(db) / 1024 / 1024
|
||||||
|
print(f" 消息库: {len([d for d in dbs if 'message' in d])} 个 ({sz:.0f} MB)")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print("[decrypt] 未解密 (运行: python main.py decrypt)")
|
||||||
|
|
||||||
|
exported_dir = "exported_chats"
|
||||||
|
if os.path.exists(exported_dir):
|
||||||
|
jsons = [f for f in glob.glob(os.path.join(exported_dir, "*.json"))
|
||||||
|
if not f.endswith("_transcribed.json")]
|
||||||
|
tx_jsons = glob.glob(os.path.join(exported_dir, "*_transcribed.json"))
|
||||||
|
total_sz = sum(os.path.getsize(f) for f in jsons) / 1024 / 1024
|
||||||
|
print(f"[export] {len(jsons)} 个 JSON ({total_sz:.0f} MB)")
|
||||||
|
else:
|
||||||
|
print("[export] 未导出 (运行: python main.py export)")
|
||||||
|
|
||||||
|
if os.path.exists(exported_dir):
|
||||||
|
total_voice = 0
|
||||||
|
total_tx = 0
|
||||||
|
for jp in glob.glob(os.path.join(exported_dir, "*_transcribed.json")):
|
||||||
|
try:
|
||||||
|
with open(jp, encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if isinstance(data, dict) and "chats" in data:
|
||||||
|
for chat in data["chats"]:
|
||||||
|
for m in chat.get("messages", []):
|
||||||
|
if m.get("type") == "voice":
|
||||||
|
total_voice += 1
|
||||||
|
if m.get("transcription"):
|
||||||
|
total_tx += 1
|
||||||
|
elif isinstance(data, dict):
|
||||||
|
for m in data.get("messages", []):
|
||||||
|
if m.get("type") == "voice":
|
||||||
|
total_voice += 1
|
||||||
|
if m.get("transcription"):
|
||||||
|
total_tx += 1
|
||||||
|
if total_voice > 0:
|
||||||
|
pct = total_tx * 100 // max(total_voice, 1)
|
||||||
|
print(f"[transcribe] {total_tx}/{total_voice} ({pct}%) 条语音已转录")
|
||||||
|
|
||||||
|
# 建议的下一步
|
||||||
|
print()
|
||||||
|
steps = []
|
||||||
|
if not os.path.exists(decrypted_dir):
|
||||||
|
steps.append("python main.py decrypt — 解密数据库")
|
||||||
|
elif not os.path.exists(exported_dir):
|
||||||
|
steps.append("main.py export — 导出聊天记录")
|
||||||
|
if steps:
|
||||||
|
print("建议的下一步:")
|
||||||
|
for s in steps:
|
||||||
|
print(f" {s}")
|
||||||
|
else:
|
||||||
|
print("所有步骤已完成。")
|
||||||
|
|
||||||
|
|
||||||
|
def print_usage():
|
||||||
|
print("用法:")
|
||||||
|
print(" python main.py 启动实时消息监听 (Web UI)")
|
||||||
|
print(" python main.py decrypt 解密全部数据库到 decrypted/")
|
||||||
|
print(" python main.py decode-images 批量解密 .dat 图片到 decoded_image_dir/")
|
||||||
|
print(" python main.py decode-images --help 查看 decode-images 全部选项")
|
||||||
|
print(" python main.py export 解密 + 批量导出聊天记录")
|
||||||
|
print(" python main.py all 从零到完成:密钥 → 解密 → 导出")
|
||||||
|
print(" python main.py status 显示当前状态和磁盘用量")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
print(" WeChat Decrypt")
|
print(" WeChat Decrypt")
|
||||||
print("=" * 60)
|
print("=" * 60)
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# 1. 加载配置(自动检测 db_dir)
|
cmd = sys.argv[1] if len(sys.argv) > 1 else "web"
|
||||||
|
|
||||||
|
# help / status 不需要密钥和微信进程
|
||||||
|
if cmd in ("help", "-h", "--help"):
|
||||||
|
print_usage()
|
||||||
|
return
|
||||||
|
if cmd in ("status", "-s"):
|
||||||
|
show_status()
|
||||||
|
return
|
||||||
|
|
||||||
|
# 以下命令需要配置 + 微信进程
|
||||||
from config import load_config
|
from config import load_config
|
||||||
cfg = load_config()
|
cfg = load_config()
|
||||||
|
|
||||||
@@ -189,30 +295,52 @@ def main():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
print("[+] 微信进程运行中")
|
print("[+] 微信进程运行中")
|
||||||
|
|
||||||
# 3. 提取密钥
|
|
||||||
ensure_keys(cfg["keys_file"], cfg["db_dir"])
|
ensure_keys(cfg["keys_file"], cfg["db_dir"])
|
||||||
|
|
||||||
# 4. 根据子命令执行
|
|
||||||
cmd = sys.argv[1] if len(sys.argv) > 1 else "web"
|
|
||||||
|
|
||||||
if cmd == "decrypt":
|
if cmd == "decrypt":
|
||||||
print("[*] 开始解密全部数据库...")
|
print("[*] 开始解密全部数据库...")
|
||||||
print()
|
print()
|
||||||
from decrypt_db import main as decrypt_all
|
from decrypt_db import main as decrypt_all
|
||||||
decrypt_all()
|
decrypt_all()
|
||||||
|
|
||||||
|
elif cmd in ("export", "all"):
|
||||||
|
print("[*] 开始解密全部数据库...")
|
||||||
|
print()
|
||||||
|
from decrypt_db import main as decrypt_all
|
||||||
|
decrypt_all()
|
||||||
|
print()
|
||||||
|
print("[*] 开始批量导出聊天记录...")
|
||||||
|
print()
|
||||||
|
from export_all_chats import main as export_all
|
||||||
|
try:
|
||||||
|
export_all()
|
||||||
|
except SystemExit:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if cmd == "all" and os.path.exists("exported_chats"):
|
||||||
|
print()
|
||||||
|
print("[*] 检查语音转录配置...")
|
||||||
|
from config import load_config
|
||||||
|
cfg2 = load_config()
|
||||||
|
from mcp_server import _resolve_active_backend
|
||||||
|
backend = _resolve_active_backend()
|
||||||
|
if backend and backend != "local":
|
||||||
|
print(f" 检测到 backend = {backend}")
|
||||||
|
print(" 如需转录语音,运行: python export_all_chats.py --with-transcriptions")
|
||||||
|
else:
|
||||||
|
print(" 未配置语音转录 backend (config.json 中设置)")
|
||||||
|
print(" 配置后运行: python export_all_chats.py --with-transcriptions")
|
||||||
|
|
||||||
elif cmd == "web":
|
elif cmd == "web":
|
||||||
print("[*] 启动 Web UI...")
|
print("[*] 启动 Web UI...")
|
||||||
print()
|
print()
|
||||||
from monitor_web import main as start_web
|
from monitor_web import main as start_web
|
||||||
start_web()
|
start_web()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"[!] 未知命令: {cmd}")
|
print(f"[!] 未知命令: {cmd}")
|
||||||
print()
|
print()
|
||||||
print("用法:")
|
print_usage()
|
||||||
print(" python main.py 启动实时消息监听 (Web UI)")
|
|
||||||
print(" python main.py decrypt 解密全部数据库到 decrypted/")
|
|
||||||
print(" python main.py decode-images 批量解密 .dat 图片到 decoded_image_dir/")
|
|
||||||
print(" python main.py decode-images --help 查看 decode-images 全部选项")
|
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user