解锁并提取Linux客户端微信数据库 (vibe coded)
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"]