feat: 支持双模式 exe 入口

This commit is contained in:
phoenixray2000
2026-05-18 01:53:20 +08:00
committed by ylytdeng
parent 728dbb72df
commit f3c32d4ca2
8 changed files with 363 additions and 27 deletions

139
wechat_decrypt_launcher.py Normal file
View File

@@ -0,0 +1,139 @@
#!/usr/bin/env python3
"""WeChatDecrypt.exe 双模式入口。
无参数时启动 Web UI带参数时分发到现有 CLI 脚本。该入口同时兼容
PyInstaller onefile 里 Web UI 子进程使用的 ``sys.executable script.py``
调用形态。
"""
import os
import runpy
import sys
MAIN_COMMANDS = {"decrypt", "export", "all", "status", "-s", "decode-images"}
HELP_COMMANDS = {"help", "-h", "--help"}
SCRIPT_ENTRYPOINTS = {
"main.py",
"monitor_web.py",
"export_all_chats.py",
"find_image_key.py",
"decrypt_sns.py",
"export_sns.py",
"find_wxwork_keys.py",
"decrypt_wxwork_db.py",
"export_wxwork_messages.py",
"voice_to_mp3.py",
"batch_decrypt_images.py",
"decrypt_db.py",
}
def _app_base_dir():
if getattr(sys, "frozen", False):
return os.path.dirname(sys.executable)
return os.path.dirname(os.path.abspath(__file__))
def _script_dir():
return getattr(sys, "_MEIPASS", _app_base_dir())
def _prepare_runtime():
base_dir = _app_base_dir()
os.environ["WECHAT_DECRYPT_APP_DIR"] = base_dir
os.chdir(base_dir)
def _configure_stdio():
for stream in (sys.stdout, sys.stderr):
if hasattr(stream, "reconfigure"):
stream.reconfigure(encoding="utf-8", errors="replace")
def print_usage(stream=None):
stream = stream or sys.stdout
stream.write(
"WeChatDecrypt.exe 用法:\n"
"\n"
" WeChatDecrypt.exe 启动 Web UI\n"
" WeChatDecrypt.exe web 启动 Web UI\n"
" WeChatDecrypt.exe status 查看状态\n"
" WeChatDecrypt.exe decrypt 提取密钥并解密数据库\n"
" WeChatDecrypt.exe export [args...] 解密并批量导出聊天\n"
" WeChatDecrypt.exe all 密钥 -> 解密 -> 导出\n"
" WeChatDecrypt.exe decode-images [args...] 批量解密 .dat 图片\n"
" WeChatDecrypt.exe export-all [args...] 直接调用 export_all_chats.py\n"
"\n"
"兼容脚本式调用:\n"
" WeChatDecrypt.exe main.py decrypt\n"
" WeChatDecrypt.exe export_all_chats.py --write-plan-csv export_plan.csv\n"
)
def _resolve_script(argv):
if not argv:
return "monitor_web.py", []
cmd = argv[0]
if cmd == "web":
return "monitor_web.py", argv[1:]
if cmd in MAIN_COMMANDS:
return "main.py", argv
if cmd == "export-all":
return "export_all_chats.py", argv[1:]
if cmd in SCRIPT_ENTRYPOINTS:
return cmd, argv[1:]
return None, None
def run_script(script, script_args):
_prepare_runtime()
script_path = os.path.join(_script_dir(), script)
if not os.path.exists(script_path):
script_path = os.path.join(_app_base_dir(), script)
if not os.path.exists(script_path):
raise FileNotFoundError(f"脚本不存在: {script_path}")
old_argv = sys.argv[:]
try:
sys.argv = [script] + list(script_args)
try:
runpy.run_path(script_path, run_name="__main__")
except SystemExit as exc:
if exc.code is None:
return 0
if isinstance(exc.code, int):
return exc.code
print(exc.code, file=sys.stderr)
return 1
return 0
finally:
sys.argv = old_argv
def dispatch(argv=None, script_runner=None, stdout=None, stderr=None):
argv = list(sys.argv[1:] if argv is None else argv)
stdout = stdout or sys.stdout
stderr = stderr or sys.stderr
runner = script_runner or run_script
if argv and argv[0] in HELP_COMMANDS:
print_usage(stdout)
return 0
script, script_args = _resolve_script(argv)
if script is None:
print_usage(stdout)
print(f"未知命令: {argv[0]}", file=stderr)
return 2
return runner(script, script_args)
def main():
_configure_stdio()
sys.exit(dispatch())
if __name__ == "__main__":
main()