解锁并提取Linux客户端微信数据库 (vibe coded)
at 208 lines 8.3 kB view raw
1# -*- coding: utf-8 -*-# 2# ------------------------------------------------------------------------------- 3# Name: __init__.py 4# Description: 5# Author: xaoyaoo 6# Date: 2023/12/14 7# ------------------------------------------------------------------------------- 8import os 9import subprocess 10import sys 11import time 12import uvicorn 13import mimetypes 14import logging 15from logging.handlers import RotatingFileHandler 16 17from uvicorn.config import LOGGING_CONFIG 18from fastapi import FastAPI, Request, Path, Query 19from fastapi.staticfiles import StaticFiles 20from fastapi.exceptions import RequestValidationError 21from starlette.middleware.cors import CORSMiddleware 22from starlette.responses import RedirectResponse, FileResponse 23 24from .config import gc 25from .helpers import is_port_in_use 26from wxdump_linux._logging import server_loger 27from .response import ReJson 28from .remote_server import rs_api 29from .local_server import ls_api 30 31from wxdump_linux import __version__ 32 33 34def gen_fastapi_app(handler): 35 app = FastAPI(title="wxdump-linux", description="Linux 版微信数据库解密工具", version=__version__, 36 contact={"name": "wxdump-linux"}, 37 license_info={"name": "MIT License"}) 38 39 web_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "ui", "web") # web文件夹路径 40 41 # 跨域 42 origins = [ 43 "http://localhost:5000", 44 "http://127.0.0.1:5000", 45 "http://localhost:8080", # 开发环境的客户端地址" 46 # "http://0.0.0.0:5000", 47 # "*" 48 ] 49 app.add_middleware( 50 CORSMiddleware, 51 allow_origins=origins, # 允许所有源 52 allow_credentials=True, 53 allow_methods=["*"], # 允许所有方法 54 allow_headers=["*"], # 允许所有头 55 ) 56 57 @app.on_event("startup") 58 async def startup_event(): 59 logger = logging.getLogger("uvicorn") 60 logger.addHandler(handler) 61 62 # 错误处理 63 @app.exception_handler(RequestValidationError) 64 async def request_validation_exception_handler(request: Request, exc: RequestValidationError): 65 # print(request.body) 66 return ReJson(1002, {"detail": exc.errors()}) 67 68 # 首页 69 @app.get("/") 70 @app.get("/index.html") 71 async def index(): 72 response = RedirectResponse(url="/s/index.html", status_code=307) 73 return response 74 75 # 路由挂载 76 app.include_router(rs_api, prefix='/api/rs', tags=['远程api']) 77 app.include_router(ls_api, prefix='/api/ls', tags=['本地api']) 78 79 # 根据文件类型,设置mime_type,返回文件 80 @app.get("/s/{filename:path}") 81 async def serve_file(filename: str): 82 # 构建完整的文件路径 83 file_path = os.path.join(web_path, filename) 84 file_path = os.path.abspath(file_path) 85 86 # 检查文件是否存在 87 if os.path.isfile(file_path): 88 # 获取文件 MIME 类型 89 mime_type, _ = mimetypes.guess_type(file_path) 90 # 如果 MIME 类型为空,则默认为 application/octet-stream 91 if mime_type is None: 92 mime_type = "application/octet-stream" 93 server_loger.warning(f"[+] 无法获取文件 MIME 类型,使用默认值:{mime_type}") 94 if file_path.endswith(".js"): 95 mime_type = "text/javascript" 96 server_loger.info(f"[+] 文件 {file_path} MIME 类型:{mime_type}") 97 # 返回文件 98 return FileResponse(file_path, media_type=mime_type) 99 100 # 如果文件不存在,返回 404 101 return {"detail": "Not Found"}, 404 102 103 # 静态文件挂载 104 # if os.path.exists(os.path.join(web_path, "index.html")): 105 # app.mount("/s", StaticFiles(directory=web_path), name="static") 106 107 return app 108 109 110def start_server(port=5000, online=False, debug=False, isopenBrowser=True, 111 merge_path="", wx_path="", my_wxid="", ): 112 """ 113 启动flask 114 :param port: 端口号 115 :param online: 是否在线查看(局域网查看) 116 :param debug: 是否开启debug模式 117 :param isopenBrowser: 是否自动打开浏览器 118 :return: 119 """ 120 work_path = os.path.join(os.getcwd(), "wxdump_work") # 临时文件夹,用于存放图片等 # 全局变量 121 if not os.path.exists(work_path): 122 os.makedirs(work_path, exist_ok=True) 123 server_loger.info(f"[+] 创建临时文件夹:{work_path}") 124 print(f"[+] 创建临时文件夹:{work_path}") 125 126 # 日志处理,写入到文件 127 log_format = '[{levelname[0]}] {asctime} [{name}:{levelno}] {pathname}:{lineno} {message}' 128 log_datefmt = '%Y-%m-%d %H:%M:%S' 129 log_file_path = os.path.join(work_path, "wxdump.log") 130 file_handler = RotatingFileHandler(log_file_path, mode="a", maxBytes=10 * 1024 * 1024, backupCount=3) 131 formatter = logging.Formatter(fmt=log_format, datefmt=log_datefmt, style='{') 132 file_handler.setFormatter(formatter) 133 134 wx_core_logger = logging.getLogger("wx_core") 135 db_prepare = logging.getLogger("db_prepare") 136 137 # 这几个日志处理器为本项目的日志处理器 138 server_loger.addHandler(file_handler) 139 wx_core_logger.addHandler(file_handler) 140 db_prepare.addHandler(file_handler) 141 142 conf_file = os.path.join(work_path, "conf_auto.json") # 用于存放各种基础信息 143 auto_setting = "auto_setting" 144 env_file = os.path.join(work_path, ".env") # 用于存放环境变量 145 # set 环境变量 146 os.environ["WXDUMP_WORK_PATH"] = work_path 147 os.environ["WXDUMP_CONF_FILE"] = conf_file 148 os.environ["WXDUMP_AUTO_SETTING"] = auto_setting 149 150 with open(env_file, "w", encoding="utf-8") as f: 151 f.write(f"WXDUMP_WORK_PATH = '{work_path}'\n") 152 f.write(f"WXDUMP_CONF_FILE = '{conf_file}'\n") 153 f.write(f"WXDUMP_AUTO_SETTING = '{auto_setting}'\n") 154 155 if merge_path and os.path.exists(merge_path): 156 my_wxid = my_wxid if my_wxid else "wxid_dbshow" 157 gc.set_conf(my_wxid, "wxid", my_wxid) # 初始化wxid 158 gc.set_conf(my_wxid, "merge_path", merge_path) # 初始化merge_path 159 gc.set_conf(my_wxid, "wx_path", wx_path) # 初始化wx_path 160 db_config = {"key": my_wxid, "type": "sqlite", "path": merge_path} 161 gc.set_conf(my_wxid, "db_config", db_config) # 初始化db_config 162 gc.set_conf(auto_setting, "last", my_wxid) # 初始化last 163 164 # 检查端口是否被占用 165 if online: 166 host = '0.0.0.0' 167 else: 168 host = "127.0.0.1" 169 170 if is_port_in_use(host, port): 171 server_loger.error(f"Port {port} is already in use. Choose a different port.") 172 print(f"Port {port} is already in use. Choose a different port.") 173 input("Press Enter to exit...") 174 return # 退出程序 175 if isopenBrowser: 176 try: 177 # 自动打开浏览器 178 url = f"http://127.0.0.1:{port}/" 179 # 根据操作系统使用不同的命令打开默认浏览器 180 if sys.platform.startswith('darwin'): # macOS 181 subprocess.call(['open', url]) 182 elif sys.platform.startswith('win'): # Windows 183 subprocess.call(['start', url], shell=True) 184 elif sys.platform.startswith('linux'): # Linux 185 subprocess.call(['xdg-open', url]) 186 else: 187 server_loger.error(f"Unsupported platform, can't open browser automatically.", exc_info=True) 188 print("Unsupported platform, can't open browser automatically.") 189 except Exception as e: 190 server_loger.error(f"自动打开浏览器失败:{e}", exc_info=True) 191 192 time.sleep(1) 193 server_loger.info(f"启动flask服务,host:port:{host}:{port}") 194 print("[+] 请使用浏览器访问 http://127.0.0.1:5000/ 查看聊天记录") 195 global app 196 print("[+] 如需查看api文档,请访问 http://127.0.0.1:5000/docs ") 197 app = gen_fastapi_app(file_handler) 198 199 LOGGING_CONFIG["formatters"]["default"]["fmt"] = "[%(asctime)s] %(levelprefix)s %(message)s" 200 LOGGING_CONFIG["formatters"]["access"][ 201 "fmt"] = '[%(asctime)s] %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s' 202 203 uvicorn.run(app=app, host=host, port=port, reload=debug, log_level="info", workers=1, env_file=env_file) 204 205 206app = None 207 208__all__ = ["start_server", "gen_fastapi_app"]