该flask为fastapi,速度更快
This commit is contained in:
parent
81832a2a99
commit
0b937a2f11
@ -5,15 +5,8 @@
|
||||
# Author: xaoyaoo
|
||||
# Date: 2023/10/14
|
||||
# -------------------------------------------------------------------------------
|
||||
# from .analyzer.db_parsing import read_img_dat, read_emoji, decompress_CompressContent, read_audio_buf, read_audio, \
|
||||
# parse_xml_string, read_BytesExtra
|
||||
# from .ui import app_show_chat, get_user_list, export
|
||||
from .wx_core import BiasAddr, get_wx_info, get_wx_db, batch_decrypt, decrypt, get_core_db
|
||||
from .wx_core import merge_db, decrypt_merge, merge_real_time_db, all_merge_real_time_db
|
||||
from .analyzer import DBPool
|
||||
from .db import MsgHandler, MicroHandler, \
|
||||
MediaHandler, OpenIMContactHandler, FavoriteHandler, PublicMsgHandler, DBHandler
|
||||
from .server import start_falsk
|
||||
__version__ = "3.1.18"
|
||||
|
||||
import os, json
|
||||
|
||||
try:
|
||||
@ -24,7 +17,18 @@ except:
|
||||
WX_OFFS = {}
|
||||
WX_OFFS_PATH = None
|
||||
|
||||
from .wx_core import BiasAddr, get_wx_info, get_wx_db, batch_decrypt, decrypt, get_core_db
|
||||
from .wx_core import merge_db, decrypt_merge, merge_real_time_db, all_merge_real_time_db
|
||||
from .db import DBHandler, MsgHandler, MicroHandler, MediaHandler, OpenIMContactHandler, FavoriteHandler, \
|
||||
PublicMsgHandler
|
||||
from .api import start_server, app
|
||||
from .analyzer import DBPool
|
||||
|
||||
# PYWXDUMP_ROOT_PATH = os.path.dirname(__file__)
|
||||
# db_init = DBPool("DBPOOL_INIT")
|
||||
|
||||
__version__ = "3.1.18"
|
||||
|
||||
__all__ = ["BiasAddr", "get_wx_info", "get_wx_db", "batch_decrypt", "decrypt", "get_core_db",
|
||||
"merge_db", "decrypt_merge", "merge_real_time_db", "all_merge_real_time_db",
|
||||
"MsgHandler", "MicroHandler", "MediaHandler", "OpenIMContactHandler", "FavoriteHandler", "PublicMsgHandler",
|
||||
"DBHandler", "start_server", "WX_OFFS", "WX_OFFS_PATH", "__version__", "app"]
|
||||
|
@ -5,9 +5,132 @@
|
||||
# Author: xaoyaoo
|
||||
# Date: 2023/12/14
|
||||
# -------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import uvicorn
|
||||
|
||||
from fastapi import FastAPI, Request, Path, Query
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from starlette.middleware.cors import CORSMiddleware
|
||||
from starlette.responses import RedirectResponse
|
||||
|
||||
from .utils import gc, is_port_in_use, server_loger
|
||||
from .rjson import ReJson
|
||||
from .remote_server import rs_api
|
||||
from .local_server import ls_api
|
||||
from .utils import get_conf, set_conf
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
from pywxdump import __version__
|
||||
|
||||
app = FastAPI(title="pywxdump", description="微信工具", version=__version__,
|
||||
terms_of_service="https://github.com/xaoyaoo/pywxdump",
|
||||
contact={"name": "xaoyaoo", "url": "https://github.com/xaoyaoo/pywxdump", "email": "<EMAIL>"},
|
||||
license_info={"name": "MIT License", "url": "https://github.com/xaoyaoo/pywxdump/blob/main/LICENSE"})
|
||||
|
||||
# 跨域
|
||||
origins = [
|
||||
"http://localhost:5000",
|
||||
"http://127.0.0.1:5000",
|
||||
"http://localhost:8080", # 开发环境的客户端地址"
|
||||
# "http://0.0.0.0:5000",
|
||||
# "*"
|
||||
]
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=origins, # 允许所有源
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"], # 允许所有方法
|
||||
allow_headers=["*"], # 允许所有头
|
||||
)
|
||||
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def request_validation_exception_handler(request: Request, exc: RequestValidationError):
|
||||
# print(request.body)
|
||||
return ReJson(1002, {"detail": exc.errors()})
|
||||
|
||||
|
||||
@app.get("/")
|
||||
@app.get("/index.html")
|
||||
async def redirect():
|
||||
response = RedirectResponse(url="/s/index.html", status_code=307)
|
||||
return response
|
||||
|
||||
|
||||
# 路由挂载
|
||||
app.include_router(rs_api, prefix='/api/rs', tags=['远程api'])
|
||||
app.include_router(ls_api, prefix='/api/ls', tags=['本地api'])
|
||||
|
||||
web_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "ui", "web")
|
||||
# 静态文件挂载
|
||||
app.mount("/s", StaticFiles(directory=web_path), name="static")
|
||||
|
||||
|
||||
def start_server(port=5000, online=False, debug=False, isopenBrowser=True):
|
||||
"""
|
||||
启动flask
|
||||
:param port: 端口号
|
||||
:param online: 是否在线查看(局域网查看)
|
||||
:param debug: 是否开启debug模式
|
||||
:param isopenBrowser: 是否自动打开浏览器
|
||||
:return:
|
||||
"""
|
||||
# 全局变量
|
||||
work_path = os.path.join(os.getcwd(), "wxdump_work") # 临时文件夹,用于存放图片等
|
||||
if not os.path.exists(work_path):
|
||||
os.makedirs(work_path)
|
||||
server_loger.info(f"[+] 创建临时文件夹:{work_path}")
|
||||
print(f"[+] 创建临时文件夹:{work_path}")
|
||||
conf_file = os.path.join(work_path, "conf_auto.json") # 用于存放各种基础信息
|
||||
auto_setting = "auto_setting"
|
||||
env_file = os.path.join(work_path, ".env") # 用于存放环境变量
|
||||
# set 环境变量
|
||||
os.environ["PYWXDUMP_WORK_PATH"] = work_path
|
||||
os.environ["PYWXDUMP_CONF_FILE"] = conf_file
|
||||
os.environ["PYWXDUMP_AUTO_SETTING"] = auto_setting
|
||||
|
||||
with open(env_file, "w", encoding="utf-8") as f:
|
||||
f.write(f"PYWXDUMP_WORK_PATH = '{work_path}'\n")
|
||||
f.write(f"PYWXDUMP_CONF_FILE = '{conf_file}'\n")
|
||||
f.write(f"PYWXDUMP_AUTO_SETTING = '{auto_setting}'\n")
|
||||
|
||||
# 检查端口是否被占用
|
||||
if online:
|
||||
host = '0.0.0.0'
|
||||
else:
|
||||
host = "127.0.0.1"
|
||||
|
||||
if is_port_in_use(host, port):
|
||||
server_loger.error(f"Port {port} is already in use. Choose a different port.")
|
||||
print(f"Port {port} is already in use. Choose a different port.")
|
||||
input("Press Enter to exit...")
|
||||
return # 退出程序
|
||||
if isopenBrowser:
|
||||
try:
|
||||
# 自动打开浏览器
|
||||
url = f"http://127.0.0.1:{port}/"
|
||||
# 根据操作系统使用不同的命令打开默认浏览器
|
||||
if sys.platform.startswith('darwin'): # macOS
|
||||
subprocess.call(['open', url])
|
||||
elif sys.platform.startswith('win'): # Windows
|
||||
subprocess.call(['start', url], shell=True)
|
||||
elif sys.platform.startswith('linux'): # Linux
|
||||
subprocess.call(['xdg-open', url])
|
||||
else:
|
||||
server_loger.error(f"Unsupported platform, can't open browser automatically.", exc_info=True)
|
||||
print("Unsupported platform, can't open browser automatically.")
|
||||
except Exception as e:
|
||||
server_loger.error(f"自动打开浏览器失败:{e}", exc_info=True)
|
||||
|
||||
time.sleep(1)
|
||||
server_loger.info(f"启动flask服务,host:port:{host}:{port}")
|
||||
print("[+] 请使用浏览器访问 http://127.0.0.1:5000/ 查看聊天记录")
|
||||
|
||||
uvicorn.run(app=app, host=host, port=port, reload=debug, log_level="info", workers=1, env_file=env_file)
|
||||
|
||||
|
||||
__all__ = ["start_server", "app"]
|
||||
|
@ -5,58 +5,55 @@
|
||||
# Author: xaoyaoo
|
||||
# Date: 2024/08/01
|
||||
# -------------------------------------------------------------------------------
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import shutil
|
||||
import pythoncom
|
||||
import pywxdump
|
||||
|
||||
from flask import Flask, request, render_template, g, Blueprint, send_file, make_response, session
|
||||
from pywxdump import get_core_db, all_merge_real_time_db, get_wx_db
|
||||
from pywxdump.api.rjson import ReJson, RqJson
|
||||
from pywxdump.api.utils import get_conf, get_conf_wxids, set_conf, error9999, gen_base64, validate_title, \
|
||||
get_conf_local_wxid, ls_loger, random_str
|
||||
from pywxdump import get_wx_info, WX_OFFS, batch_decrypt, BiasAddr, merge_db, decrypt_merge, merge_real_time_db
|
||||
ls_api = Blueprint('ls_api', __name__, template_folder='../ui/web', static_folder='../ui/web/assets/', )
|
||||
ls_api.debug = False
|
||||
from pydantic import BaseModel
|
||||
from fastapi import APIRouter
|
||||
|
||||
from pywxdump import all_merge_real_time_db, get_wx_db
|
||||
from pywxdump import get_wx_info, batch_decrypt, BiasAddr, merge_db, decrypt_merge
|
||||
|
||||
from .rjson import ReJson, RqJson
|
||||
from .utils import error9999, ls_loger, random_str, gc
|
||||
|
||||
ls_api = APIRouter()
|
||||
|
||||
|
||||
# 以下为初始化相关 *******************************************************************************************************
|
||||
|
||||
@ls_api.route('/api/ls/init_last_local_wxid', methods=["GET", 'POST'])
|
||||
@ls_api.api_route('/init_last_local_wxid', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def init_last_local_wxid():
|
||||
"""
|
||||
初始化,包括key
|
||||
:return:
|
||||
"""
|
||||
local_wxid = get_conf_local_wxid(g.caf)
|
||||
local_wxid.remove(g.at)
|
||||
local_wxid = gc.get_local_wxids()
|
||||
local_wxid.remove(gc.at)
|
||||
if local_wxid:
|
||||
return ReJson(0, {"local_wxids": local_wxid})
|
||||
return ReJson(0, {"local_wxids": []})
|
||||
|
||||
|
||||
@ls_api.route('/api/ls/init_last', methods=["GET", 'POST'])
|
||||
@ls_api.api_route('/init_last', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def init_last():
|
||||
def init_last(my_wxid: str):
|
||||
"""
|
||||
是否初始化
|
||||
:return:
|
||||
"""
|
||||
my_wxid = request.json.get("my_wxid", "")
|
||||
my_wxid = my_wxid.strip().strip("'").strip('"') if isinstance(my_wxid, str) else ""
|
||||
if not my_wxid:
|
||||
my_wxid = get_conf(g.caf, "auto_setting", "last")
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
if my_wxid:
|
||||
set_conf(g.caf, "auto_setting", "last", my_wxid)
|
||||
merge_path = get_conf(g.caf, my_wxid, "merge_path")
|
||||
wx_path = get_conf(g.caf, my_wxid, "wx_path")
|
||||
key = get_conf(g.caf, my_wxid, "key")
|
||||
gc.set_conf(gc.at, "last", my_wxid)
|
||||
merge_path = gc.get_conf(my_wxid, "merge_path")
|
||||
wx_path = gc.get_conf(my_wxid, "wx_path")
|
||||
key = gc.get_conf(my_wxid, "key")
|
||||
rdata = {
|
||||
"merge_path": merge_path,
|
||||
"wx_path": wx_path,
|
||||
@ -69,16 +66,23 @@ def init_last():
|
||||
return ReJson(0, {"is_init": False, "my_wxid": ""})
|
||||
|
||||
|
||||
@ls_api.route('/api/ls/init_key', methods=["GET", 'POST'])
|
||||
class InitKeyRequest(BaseModel):
|
||||
wx_path: str
|
||||
key: str
|
||||
my_wxid: str
|
||||
|
||||
|
||||
@ls_api.api_route('/init_key', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def init_key():
|
||||
def init_key(request: InitKeyRequest):
|
||||
"""
|
||||
初始化,包括key
|
||||
初始化key
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
wx_path = request.json.get("wx_path", "").strip().strip("'").strip('"')
|
||||
key = request.json.get("key", "").strip().strip("'").strip('"')
|
||||
my_wxid = request.json.get("my_wxid", "").strip().strip("'").strip('"')
|
||||
wx_path = request.wx_path.strip().strip("'").strip('"')
|
||||
key = request.key.strip().strip("'").strip('"')
|
||||
my_wxid = request.my_wxid.strip().strip("'").strip('"')
|
||||
if not wx_path:
|
||||
return ReJson(1002, body=f"wx_path is required: {wx_path}")
|
||||
if not os.path.exists(wx_path):
|
||||
@ -92,8 +96,10 @@ def init_key():
|
||||
# if isinstance(db_config, dict) and db_config and os.path.exists(db_config.get("path")):
|
||||
# pmsg = DBHandler(db_config)
|
||||
# # pmsg.close_all_connection()
|
||||
print(id(gc))
|
||||
print(wx_path, "\n", key, "\n", my_wxid, "\n", gc.work_path)
|
||||
|
||||
out_path = os.path.join(g.work_path, "decrypted", my_wxid) if my_wxid else os.path.join(g.work_path, "decrypted")
|
||||
out_path = os.path.join(gc.work_path, "decrypted", my_wxid) if my_wxid else os.path.join(gc.work_path, "decrypted")
|
||||
# 检查文件夹中文件是否被占用
|
||||
if os.path.exists(out_path):
|
||||
try:
|
||||
@ -107,9 +113,9 @@ def init_key():
|
||||
time.sleep(1)
|
||||
if code:
|
||||
# 移动merge_save_path到g.work_path/my_wxid
|
||||
if not os.path.exists(os.path.join(g.work_path, my_wxid)):
|
||||
os.makedirs(os.path.join(g.work_path, my_wxid))
|
||||
merge_save_path_new = os.path.join(g.work_path, my_wxid, "merge_all.db")
|
||||
if not os.path.exists(os.path.join(gc.work_path, my_wxid)):
|
||||
os.makedirs(os.path.join(gc.work_path, my_wxid))
|
||||
merge_save_path_new = os.path.join(gc.work_path, my_wxid, "merge_all.db")
|
||||
shutil.move(merge_save_path, str(merge_save_path_new))
|
||||
|
||||
# 删除out_path
|
||||
@ -124,12 +130,13 @@ def init_key():
|
||||
"type": "sqlite",
|
||||
"path": merge_save_path_new
|
||||
}
|
||||
set_conf(g.caf, my_wxid, "db_config", db_config)
|
||||
set_conf(g.caf, my_wxid, "merge_path", merge_save_path_new)
|
||||
set_conf(g.caf, my_wxid, "wx_path", wx_path)
|
||||
set_conf(g.caf, my_wxid, "key", key)
|
||||
set_conf(g.caf, my_wxid, "my_wxid", my_wxid)
|
||||
set_conf(g.caf, "auto_setting", "last", my_wxid)
|
||||
gc.set_conf(my_wxid, "db_config", db_config)
|
||||
gc.set_conf(my_wxid, "db_config", db_config)
|
||||
gc.set_conf(my_wxid, "merge_path", merge_save_path_new)
|
||||
gc.set_conf(my_wxid, "wx_path", wx_path)
|
||||
gc.set_conf(my_wxid, "key", key)
|
||||
gc.set_conf(my_wxid, "my_wxid", my_wxid)
|
||||
gc.set_conf(gc.at, "last", my_wxid)
|
||||
rdata = {
|
||||
"merge_path": merge_save_path_new,
|
||||
"wx_path": wx_path,
|
||||
@ -142,16 +149,22 @@ def init_key():
|
||||
return ReJson(2001, body=merge_save_path)
|
||||
|
||||
|
||||
@ls_api.route('/api/ls/init_nokey', methods=["GET", 'POST'])
|
||||
class InitNoKeyRequest(BaseModel):
|
||||
merge_path: str
|
||||
wx_path: str
|
||||
my_wxid: str
|
||||
|
||||
|
||||
@ls_api.post('/init_nokey')
|
||||
@error9999
|
||||
def init_nokey():
|
||||
def init_nokey(request: InitNoKeyRequest):
|
||||
"""
|
||||
初始化,包括key
|
||||
:return:
|
||||
"""
|
||||
merge_path = request.json.get("merge_path", "").strip().strip("'").strip('"')
|
||||
wx_path = request.json.get("wx_path", "").strip().strip("'").strip('"')
|
||||
my_wxid = request.json.get("my_wxid", "").strip().strip("'").strip('"')
|
||||
merge_path = request.merge_path.strip().strip("'").strip('"')
|
||||
wx_path = request.wx_path.strip().strip("'").strip('"')
|
||||
my_wxid = request.my_wxid.strip().strip("'").strip('"')
|
||||
|
||||
if not wx_path:
|
||||
return ReJson(1002, body=f"wx_path is required: {wx_path}")
|
||||
@ -162,18 +175,18 @@ def init_nokey():
|
||||
if not my_wxid:
|
||||
return ReJson(1002, body=f"my_wxid is required: {my_wxid}")
|
||||
|
||||
key = get_conf(g.caf, my_wxid, "key")
|
||||
key = gc.get_conf(my_wxid, "key")
|
||||
db_config = {
|
||||
"key": random_str(16),
|
||||
"type": "sqlite",
|
||||
"path": merge_path
|
||||
}
|
||||
set_conf(g.caf, my_wxid, "db_config", db_config)
|
||||
set_conf(g.caf, my_wxid, "merge_path", merge_path)
|
||||
set_conf(g.caf, my_wxid, "wx_path", wx_path)
|
||||
set_conf(g.caf, my_wxid, "key", key)
|
||||
set_conf(g.caf, my_wxid, "my_wxid", my_wxid)
|
||||
set_conf(g.caf, g.at, "last", my_wxid)
|
||||
gc.set_conf(my_wxid, "db_config", db_config)
|
||||
gc.set_conf(my_wxid, "merge_path", merge_path)
|
||||
gc.set_conf(my_wxid, "wx_path", wx_path)
|
||||
gc.set_conf(my_wxid, "key", key)
|
||||
gc.set_conf(my_wxid, "my_wxid", my_wxid)
|
||||
gc.set_conf(gc.at, "last", my_wxid)
|
||||
rdata = {
|
||||
"merge_path": merge_path,
|
||||
"wx_path": wx_path,
|
||||
@ -187,24 +200,24 @@ def init_nokey():
|
||||
# END 以上为初始化相关 ***************************************************************************************************
|
||||
|
||||
|
||||
@ls_api.route('/api/ls/realtimemsg', methods=["GET", "POST"])
|
||||
@ls_api.api_route('/realtimemsg', methods=["GET", "POST"])
|
||||
@error9999
|
||||
def get_real_time_msg():
|
||||
"""
|
||||
获取实时消息 使用 merge_real_time_db()函数
|
||||
:return:
|
||||
"""
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
|
||||
merge_path = get_conf(g.caf, my_wxid, "merge_path")
|
||||
key = get_conf(g.caf, my_wxid, "key")
|
||||
wx_path = get_conf(g.caf, my_wxid, "wx_path")
|
||||
merge_path = gc.get_conf(my_wxid, "merge_path")
|
||||
key = gc.get_conf(my_wxid, "key")
|
||||
wx_path = gc.get_conf(my_wxid, "wx_path")
|
||||
|
||||
if not merge_path or not key or not wx_path or not wx_path:
|
||||
return ReJson(1002, body="msg_path or media_path or wx_path or key is required")
|
||||
|
||||
real_time_exe_path = get_conf(g.caf, g.at, "real_time_exe_path")
|
||||
real_time_exe_path = gc.get_conf(gc.at, "real_time_exe_path")
|
||||
|
||||
code, ret = all_merge_real_time_db(key=key, wx_path=wx_path, merge_path=merge_path,
|
||||
real_time_exe_path=real_time_exe_path)
|
||||
@ -216,7 +229,7 @@ def get_real_time_msg():
|
||||
|
||||
# start 这部分为专业工具的api *********************************************************************************************
|
||||
|
||||
@ls_api.route('/api/ls/wxinfo', methods=["GET", 'POST'])
|
||||
@ls_api.api_route('/wxinfo', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def get_wxinfo():
|
||||
"""
|
||||
@ -224,24 +237,33 @@ def get_wxinfo():
|
||||
:return:
|
||||
"""
|
||||
import pythoncom
|
||||
pythoncom.CoInitialize()
|
||||
from pywxdump import WX_OFFS
|
||||
pythoncom.CoInitialize() # 初始化COM库
|
||||
wxinfos = get_wx_info(WX_OFFS)
|
||||
pythoncom.CoUninitialize()
|
||||
pythoncom.CoUninitialize() # 释放COM库
|
||||
return ReJson(0, wxinfos)
|
||||
|
||||
|
||||
@ls_api.route('/api/ls/biasaddr', methods=["GET", 'POST'])
|
||||
class BiasAddrRequest(BaseModel):
|
||||
mobile: str
|
||||
name: str
|
||||
account: str
|
||||
key: str = ""
|
||||
wxdbPath: str = ""
|
||||
|
||||
|
||||
@ls_api.post('/biasaddr')
|
||||
@error9999
|
||||
def biasaddr():
|
||||
def biasaddr(request: BiasAddrRequest):
|
||||
"""
|
||||
BiasAddr
|
||||
:return:
|
||||
"""
|
||||
mobile = request.json.get("mobile")
|
||||
name = request.json.get("name")
|
||||
account = request.json.get("account")
|
||||
key = request.json.get("key", "")
|
||||
wxdbPath = request.json.get("wxdbPath", "")
|
||||
mobile = request.mobile
|
||||
name = request.name
|
||||
account = request.account
|
||||
key = request.json.key
|
||||
wxdbPath = request.wxdbPath
|
||||
if not mobile or not name or not account:
|
||||
return ReJson(1002)
|
||||
pythoncom.CoInitialize()
|
||||
@ -249,39 +271,33 @@ def biasaddr():
|
||||
return ReJson(0, str(rdata))
|
||||
|
||||
|
||||
@ls_api.route('/api/ls/decrypt', methods=["GET", 'POST'])
|
||||
@ls_api.api_route('/decrypt', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def decrypt():
|
||||
def decrypt(key: str, wxdbPath: str, outPath: str = ""):
|
||||
"""
|
||||
解密
|
||||
:return:
|
||||
"""
|
||||
key = request.json.get("key")
|
||||
if not key:
|
||||
return ReJson(1002)
|
||||
wxdb_path = request.json.get("wxdbPath")
|
||||
if not wxdb_path:
|
||||
return ReJson(1002)
|
||||
out_path = request.json.get("outPath")
|
||||
if not out_path:
|
||||
out_path = g.tmp_path
|
||||
wxinfos = batch_decrypt(key, wxdb_path, out_path=out_path)
|
||||
if not outPath:
|
||||
outPath = gc.work_path
|
||||
wxinfos = batch_decrypt(key, wxdbPath, out_path=outPath)
|
||||
return ReJson(0, str(wxinfos))
|
||||
|
||||
|
||||
@ls_api.route('/api/ls/merge', methods=["GET", 'POST'])
|
||||
class MergeRequest(BaseModel):
|
||||
dbPath: str
|
||||
outPath: str
|
||||
|
||||
|
||||
@ls_api.post('/merge')
|
||||
@error9999
|
||||
def merge():
|
||||
def merge(request: MergeRequest):
|
||||
"""
|
||||
合并
|
||||
:return:
|
||||
"""
|
||||
wxdb_path = request.json.get("dbPath")
|
||||
if not wxdb_path:
|
||||
return ReJson(1002)
|
||||
out_path = request.json.get("outPath")
|
||||
if not out_path:
|
||||
return ReJson(1002)
|
||||
wxdb_path = request.dbPath
|
||||
out_path = request.outPath
|
||||
db_path = get_wx_db(wxdb_path)
|
||||
# for i in db_path:print(i)
|
||||
rdata = merge_db(db_path, out_path)
|
||||
|
@ -5,43 +5,38 @@
|
||||
# Author: xaoyaoo
|
||||
# Date: 2024/01/02
|
||||
# -------------------------------------------------------------------------------
|
||||
import base64
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
import shutil
|
||||
from collections import Counter
|
||||
from urllib.parse import quote
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
from fastapi import APIRouter,Response
|
||||
from starlette.responses import StreamingResponse, FileResponse
|
||||
|
||||
import pythoncom
|
||||
import pywxdump
|
||||
|
||||
from flask import Flask, request, render_template, g, Blueprint, send_file, make_response, session
|
||||
from pywxdump import get_core_db, all_merge_real_time_db
|
||||
from pywxdump.api.rjson import ReJson, RqJson
|
||||
from pywxdump.api.utils import get_conf, get_conf_wxids, set_conf, error9999, gen_base64, validate_title, \
|
||||
get_conf_local_wxid
|
||||
from pywxdump import get_wx_info, WX_OFFS, batch_decrypt, BiasAddr, merge_db, decrypt_merge, merge_real_time_db
|
||||
|
||||
from pywxdump import decrypt_merge,get_core_db
|
||||
from pywxdump.db import DBHandler, download_file, dat2img
|
||||
from pywxdump.db.export import export_csv, export_json, export_html
|
||||
|
||||
# app = Flask(__name__, static_folder='../ui/web/dist', static_url_path='/')
|
||||
from .rjson import ReJson, RqJson
|
||||
from .utils import error9999, gc, asyncError9999
|
||||
|
||||
rs_api = Blueprint('rs_api', __name__, template_folder='../ui/web', static_folder='../ui/web/assets/', )
|
||||
rs_api.debug = False
|
||||
rs_api = APIRouter()
|
||||
|
||||
|
||||
# 是否初始化
|
||||
@rs_api.route('/api/rs/is_init', methods=["GET", 'POST'])
|
||||
@rs_api.api_route('/is_init', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def is_init():
|
||||
"""
|
||||
是否初始化
|
||||
:return:
|
||||
"""
|
||||
local_wxids = get_conf_local_wxid(g.caf)
|
||||
|
||||
local_wxids = gc.get_local_wxids()
|
||||
if len(local_wxids) > 1:
|
||||
return ReJson(0, True)
|
||||
return ReJson(0, False)
|
||||
@ -49,72 +44,62 @@ def is_init():
|
||||
|
||||
# start 以下为聊天联系人相关api *******************************************************************************************
|
||||
|
||||
@rs_api.route('/api/rs/mywxid', methods=["GET", 'POST'])
|
||||
@rs_api.api_route('/mywxid', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def mywxid():
|
||||
"""
|
||||
获取我的微信id
|
||||
:return:
|
||||
"""
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
return ReJson(0, {"my_wxid": my_wxid})
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/user_session_list', methods=["GET", 'POST'])
|
||||
@rs_api.api_route('/user_session_list', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def user_session_list():
|
||||
"""
|
||||
获取联系人列表
|
||||
:return:
|
||||
"""
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = get_conf(g.caf, my_wxid, "db_config")
|
||||
db_config = gc.get_conf(my_wxid, "db_config")
|
||||
db = DBHandler(db_config, my_wxid=my_wxid)
|
||||
ret = db.get_session_list()
|
||||
return ReJson(0, list(ret.values()))
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/user_labels_dict', methods=["GET", 'POST'])
|
||||
@rs_api.api_route('/user_labels_dict', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def user_labels_dict():
|
||||
"""
|
||||
获取标签字典
|
||||
:return:
|
||||
"""
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = get_conf(g.caf, my_wxid, "db_config")
|
||||
db_config = gc.get_conf(my_wxid, "db_config")
|
||||
db = DBHandler(db_config, my_wxid=my_wxid)
|
||||
user_labels_dict = db.get_labels()
|
||||
return ReJson(0, user_labels_dict)
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/user_list', methods=["GET", 'POST'])
|
||||
@rs_api.post('/user_list')
|
||||
@error9999
|
||||
def user_list():
|
||||
def user_list(word: str = "", wxids: List[str] = None, labels: List[str] = None):
|
||||
"""
|
||||
获取联系人列表,可用于搜索
|
||||
:return:
|
||||
"""
|
||||
if request.method == "GET":
|
||||
word = request.args.get("word", "")
|
||||
wxids = request.args.get("wxids", [])
|
||||
labels = request.args.get("labels", [])
|
||||
elif request.method == "POST":
|
||||
word = request.json.get("word", "")
|
||||
wxids = request.json.get("wxids", [])
|
||||
labels = request.json.get("labels", [])
|
||||
else:
|
||||
return ReJson(1003, msg="Unsupported method")
|
||||
|
||||
if isinstance(wxids, str) and wxids == '' or wxids is None: wxids = []
|
||||
if isinstance(labels, str) and labels == '' or labels is None: labels = []
|
||||
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = get_conf(g.caf, my_wxid, "db_config")
|
||||
db_config = gc.get_conf(my_wxid, "db_config")
|
||||
db = DBHandler(db_config, my_wxid=my_wxid)
|
||||
users = db.get_user(word, wxids, labels)
|
||||
return ReJson(0, users)
|
||||
@ -124,26 +109,72 @@ def user_list():
|
||||
|
||||
# start 以下为聊天记录相关api *********************************************************************************************
|
||||
|
||||
class MsgCountRequest(BaseModel):
|
||||
wxids: Optional[List[str]]
|
||||
|
||||
@rs_api.route('/api/rs/imgsrc/<path:imgsrc>', methods=["GET", 'POST'])
|
||||
|
||||
@rs_api.post('/msg_count')
|
||||
@error9999
|
||||
def get_imgsrc(imgsrc):
|
||||
def msg_count(request: MsgCountRequest):
|
||||
"""
|
||||
获取联系人的聊天记录数量
|
||||
:return:
|
||||
"""
|
||||
wxids = request.wxids
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = gc.get_db_config()
|
||||
db = DBHandler(db_config, my_wxid=my_wxid)
|
||||
count = db.get_msgs_count(wxids)
|
||||
return ReJson(0, count)
|
||||
|
||||
|
||||
class MsgListRequest(BaseModel):
|
||||
wxid: str
|
||||
start: int
|
||||
limit: int
|
||||
|
||||
|
||||
@rs_api.api_route('/msg_list', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def get_msgs(request: MsgListRequest):
|
||||
"""
|
||||
获取联系人的聊天记录
|
||||
:return:
|
||||
"""
|
||||
wxid = request.wxid
|
||||
start = request.start
|
||||
limit = request.limit
|
||||
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = gc.get_conf(my_wxid, "db_config")
|
||||
|
||||
db = DBHandler(db_config, my_wxid=my_wxid)
|
||||
msgs, users = db.get_msgs(wxid=wxid, start_index=start, page_size=limit)
|
||||
return ReJson(0, {"msg_list": msgs, "user_list": users})
|
||||
|
||||
|
||||
@rs_api.get('/imgsrc')
|
||||
@asyncError9999
|
||||
async def get_imgsrc(src: str):
|
||||
"""
|
||||
获取图片,
|
||||
1. 从网络获取图片,主要功能只是下载图片,缓存到本地
|
||||
2. 读取本地图片
|
||||
:return:
|
||||
"""
|
||||
imgsrc = src
|
||||
if not imgsrc:
|
||||
return ReJson(1002)
|
||||
if imgsrc.startswith("FileStorage"): # 如果是本地图片文件则调用get_img
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
wx_path = get_conf(g.caf, my_wxid, "wx_path")
|
||||
wx_path = gc.get_conf(my_wxid, "wx_path")
|
||||
|
||||
img_path = imgsrc.replace("\\\\", "\\")
|
||||
|
||||
img_tmp_path = os.path.join(g.work_path, my_wxid, "img")
|
||||
img_tmp_path = os.path.join(gc.work_path, my_wxid, "img")
|
||||
original_img_path = os.path.join(wx_path, img_path)
|
||||
if os.path.exists(original_img_path):
|
||||
rc, fomt, md5, out_bytes = dat2img(original_img_path)
|
||||
@ -151,21 +182,21 @@ def get_imgsrc(imgsrc):
|
||||
return ReJson(1001, body=original_img_path)
|
||||
imgsavepath = os.path.join(str(img_tmp_path), img_path + "_" + "".join([md5, fomt]))
|
||||
if os.path.exists(imgsavepath):
|
||||
return send_file(imgsavepath)
|
||||
return FileResponse(imgsavepath)
|
||||
if not os.path.exists(os.path.dirname(imgsavepath)):
|
||||
os.makedirs(os.path.dirname(imgsavepath))
|
||||
with open(imgsavepath, "wb") as f:
|
||||
f.write(out_bytes)
|
||||
return send_file(imgsavepath)
|
||||
return Response(content=out_bytes, media_type="image/jpeg")
|
||||
else:
|
||||
return ReJson(1001, body=f"{original_img_path} not exists")
|
||||
elif imgsrc.startswith("http://") or imgsrc.startswith("https://"):
|
||||
# 将?后面的参数连接到imgsrc
|
||||
imgsrc = imgsrc + "?" + request.query_string.decode("utf-8") if request.query_string else imgsrc
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
|
||||
img_tmp_path = os.path.join(g.work_path, my_wxid, "imgsrc")
|
||||
img_tmp_path = os.path.join(gc.work_path, my_wxid, "imgsrc")
|
||||
if not os.path.exists(img_tmp_path):
|
||||
os.makedirs(img_tmp_path)
|
||||
file_name = imgsrc.replace("http://", "").replace("https://", "").replace("/", "_").replace("?", "_")
|
||||
@ -176,97 +207,69 @@ def get_imgsrc(imgsrc):
|
||||
|
||||
img_path_all = os.path.join(str(img_tmp_path), file_name)
|
||||
if os.path.exists(img_path_all):
|
||||
return send_file(img_path_all)
|
||||
return FileResponse(img_path_all)
|
||||
else:
|
||||
download_file(imgsrc, img_path_all)
|
||||
# proxies = {
|
||||
# "http": "http://127.0.0.1:10809",
|
||||
# "https": "http://127.0.0.1:10809",
|
||||
# }
|
||||
proxies = None
|
||||
download_file(imgsrc, img_path_all, proxies=proxies)
|
||||
if os.path.exists(img_path_all):
|
||||
return send_file(img_path_all)
|
||||
return FileResponse(img_path_all)
|
||||
else:
|
||||
return ReJson(4004, body=imgsrc)
|
||||
else:
|
||||
return ReJson(1002, body=imgsrc)
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/msg_count', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def msg_count():
|
||||
@rs_api.api_route('/video', methods=["GET", 'POST'])
|
||||
def get_video(src: str):
|
||||
"""
|
||||
获取联系人的聊天记录数量
|
||||
获取视频
|
||||
:return:
|
||||
"""
|
||||
if request.method == "GET":
|
||||
wxid = request.args.get("wxids", [])
|
||||
elif request.method == "POST":
|
||||
wxid = request.json.get("wxids", [])
|
||||
else:
|
||||
return ReJson(1003, msg="Unsupported method")
|
||||
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
videoPath = src
|
||||
if not videoPath:
|
||||
return ReJson(1002)
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = get_conf(g.caf, my_wxid, "db_config")
|
||||
db = DBHandler(db_config, my_wxid=my_wxid)
|
||||
count = db.get_msgs_count(wxid)
|
||||
return ReJson(0, count)
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/msg_list', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def get_msgs():
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = get_conf(g.caf, my_wxid, "db_config")
|
||||
|
||||
start = request.json.get("start")
|
||||
limit = request.json.get("limit")
|
||||
wxid = request.json.get("wxid")
|
||||
|
||||
if not wxid:
|
||||
return ReJson(1002, body=f"wxid is required: {wxid}")
|
||||
if start and isinstance(start, str) and start.isdigit():
|
||||
start = int(start)
|
||||
if limit and isinstance(limit, str) and limit.isdigit():
|
||||
limit = int(limit)
|
||||
if start is None or limit is None:
|
||||
return ReJson(1002, body=f"start or limit is required {start} {limit}")
|
||||
if not isinstance(start, int) and not isinstance(limit, int):
|
||||
return ReJson(1002, body=f"start or limit is not int {start} {limit}")
|
||||
|
||||
db = DBHandler(db_config, my_wxid=my_wxid)
|
||||
msgs, users = db.get_msgs(wxid=wxid, start_index=start, page_size=limit)
|
||||
return ReJson(0, {"msg_list": msgs, "user_list": users})
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/video/<path:videoPath>', methods=["GET", 'POST'])
|
||||
def get_video(videoPath):
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
wx_path = get_conf(g.caf, my_wxid, "wx_path")
|
||||
wx_path = gc.get_conf(my_wxid, "wx_path")
|
||||
|
||||
videoPath = videoPath.replace("\\\\", "\\")
|
||||
|
||||
video_tmp_path = os.path.join(g.work_path, my_wxid, "video")
|
||||
video_tmp_path = os.path.join(gc.work_path, my_wxid, "video")
|
||||
original_img_path = os.path.join(wx_path, videoPath)
|
||||
if not os.path.exists(original_img_path):
|
||||
return ReJson(5002)
|
||||
# 复制文件到临时文件夹
|
||||
assert isinstance(video_tmp_path, str)
|
||||
video_save_path = os.path.join(video_tmp_path, videoPath)
|
||||
if not os.path.exists(os.path.dirname(video_save_path)):
|
||||
os.makedirs(os.path.dirname(video_save_path))
|
||||
if os.path.exists(video_save_path):
|
||||
return send_file(video_save_path)
|
||||
return FileResponse(path=video_save_path)
|
||||
shutil.copy(original_img_path, video_save_path)
|
||||
return send_file(original_img_path)
|
||||
return FileResponse(path=video_save_path)
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/audio/<path:savePath>', methods=["GET", 'POST'])
|
||||
def get_audio(savePath):
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
@rs_api.api_route('/audio', methods=["GET", 'POST'])
|
||||
def get_audio(src: str):
|
||||
"""
|
||||
获取语音
|
||||
:return:
|
||||
"""
|
||||
savePath = src.replace("audio\\", "")
|
||||
if not savePath:
|
||||
return ReJson(1002)
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = get_conf(g.caf, my_wxid, "db_config")
|
||||
db_config = gc.get_conf(my_wxid, "db_config")
|
||||
|
||||
savePath = os.path.join(g.work_path, my_wxid, "audio", savePath) # 这个是从url中获取的
|
||||
savePath = os.path.join(gc.work_path, my_wxid, "audio", savePath) # 这个是从url中获取的
|
||||
if os.path.exists(savePath):
|
||||
return send_file(savePath)
|
||||
assert isinstance(savePath, str)
|
||||
return FileResponse(path=savePath, media_type='audio/mpeg')
|
||||
|
||||
MsgSvrID = savePath.split("_")[-1].replace(".wav", "")
|
||||
if not savePath:
|
||||
@ -282,21 +285,25 @@ def get_audio(savePath):
|
||||
return ReJson(1001, body="wave_data is required")
|
||||
|
||||
if os.path.exists(savePath):
|
||||
return send_file(savePath)
|
||||
assert isinstance(savePath, str)
|
||||
return FileResponse(path=savePath, media_type='audio/mpeg')
|
||||
else:
|
||||
return ReJson(4004, body=savePath)
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/file_info', methods=["GET", 'POST'])
|
||||
def get_file_info():
|
||||
file_path = request.args.get("file_path")
|
||||
file_path = request.json.get("file_path", file_path)
|
||||
class FileInfoRequest(BaseModel):
|
||||
file_path: str
|
||||
|
||||
|
||||
@rs_api.api_route('/file_info', methods=["GET", 'POST'])
|
||||
def get_file_info(request: FileInfoRequest):
|
||||
file_path = request.file_path
|
||||
if not file_path:
|
||||
return ReJson(1002)
|
||||
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
wx_path = get_conf(g.caf, my_wxid, "wx_path")
|
||||
wx_path = gc.get_conf(my_wxid, "wx_path")
|
||||
|
||||
all_file_path = os.path.join(wx_path, file_path)
|
||||
if not os.path.exists(all_file_path):
|
||||
@ -306,34 +313,59 @@ def get_file_info():
|
||||
return ReJson(0, {"file_name": file_name, "file_size": str(file_size)})
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/file/<path:filePath>', methods=["GET", 'POST'])
|
||||
def get_file(filePath):
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
@rs_api.get('/file')
|
||||
def get_file(src: str):
|
||||
"""
|
||||
获取文件
|
||||
:return:
|
||||
"""
|
||||
file_path = src
|
||||
if not file_path:
|
||||
return ReJson(1002)
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
wx_path = get_conf(g.caf, my_wxid, "wx_path")
|
||||
wx_path = gc.get_conf(my_wxid, "wx_path")
|
||||
|
||||
all_file_path = os.path.join(wx_path, filePath)
|
||||
all_file_path = os.path.join(wx_path, file_path)
|
||||
if not os.path.exists(all_file_path):
|
||||
return ReJson(5002)
|
||||
return send_file(all_file_path)
|
||||
|
||||
def file_iterator(file_path, chunk_size=8192):
|
||||
with open(file_path, "rb") as f:
|
||||
while True:
|
||||
chunk = f.read(chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
yield chunk
|
||||
|
||||
headers = {
|
||||
"Content-Disposition": f'attachment; filename*=UTF-8\'\'{quote(os.path.basename(all_file_path))}',
|
||||
}
|
||||
return StreamingResponse(file_iterator(all_file_path), media_type="application/octet-stream", headers=headers)
|
||||
|
||||
|
||||
# end 以上为聊天记录相关api *********************************************************************************************
|
||||
|
||||
# start 导出聊天记录 *****************************************************************************************************
|
||||
class ExportEndbRequest(BaseModel):
|
||||
wx_path: str = ""
|
||||
outpath: str = ""
|
||||
key: str = ""
|
||||
|
||||
@rs_api.route('/api/rs/export_endb', methods=["GET", 'POST'])
|
||||
def get_export_endb():
|
||||
|
||||
@rs_api.api_route('/export_endb', methods=["GET", 'POST'])
|
||||
def get_export_endb(request: ExportEndbRequest):
|
||||
"""
|
||||
导出加密数据库
|
||||
:return:
|
||||
"""
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
|
||||
wx_path = request.json.get("wx_path", "")
|
||||
wx_path = request.wx_path
|
||||
if not wx_path:
|
||||
wx_path = get_conf(g.caf, my_wxid, "wx_path")
|
||||
wx_path = gc.get_conf(my_wxid, "wx_path")
|
||||
if not os.path.exists(wx_path if wx_path else ""):
|
||||
return ReJson(1002, body=f"wx_path is required: {wx_path}")
|
||||
|
||||
@ -342,7 +374,7 @@ def get_export_endb():
|
||||
if not code:
|
||||
return ReJson(2001, body=wxdbpaths)
|
||||
|
||||
outpath = os.path.join(g.work_path, "export", my_wxid, "endb")
|
||||
outpath = os.path.join(gc.work_path, "export", my_wxid, "endb")
|
||||
if not os.path.exists(outpath):
|
||||
os.makedirs(outpath)
|
||||
|
||||
@ -354,25 +386,28 @@ def get_export_endb():
|
||||
return ReJson(0, body=outpath)
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/export_dedb', methods=["GET", "POST"])
|
||||
def get_export_dedb():
|
||||
class ExportDedbRequest(BaseModel):
|
||||
wx_path: str = ""
|
||||
outpath: str = ""
|
||||
key: str = ""
|
||||
|
||||
|
||||
@rs_api.api_route('/export_dedb', methods=["GET", "POST"])
|
||||
def get_export_dedb(request: ExportDedbRequest):
|
||||
"""
|
||||
导出解密数据库
|
||||
:return:
|
||||
"""
|
||||
if request.method not in ["GET", "POST"]:
|
||||
return ReJson(1003, msg="Unsupported method")
|
||||
rq_data = request.json if request.method == "POST" else request.args
|
||||
key = rq_data.get("key", "")
|
||||
wx_path = rq_data.get("wx_path", "")
|
||||
key = request.key
|
||||
wx_path = request.wx_path
|
||||
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
|
||||
if not key:
|
||||
key = get_conf(g.caf, my_wxid, "key")
|
||||
key = gc.get_conf(my_wxid, "key")
|
||||
if not wx_path:
|
||||
wx_path = get_conf(g.caf, my_wxid, "wx_path")
|
||||
wx_path = gc.get_conf(my_wxid, "wx_path")
|
||||
|
||||
if not key:
|
||||
return ReJson(1002, body=f"key is required: {key}")
|
||||
@ -381,7 +416,7 @@ def get_export_dedb():
|
||||
if not os.path.exists(wx_path):
|
||||
return ReJson(1001, body=f"wx_path not exists: {wx_path}")
|
||||
|
||||
outpath = os.path.join(g.work_path, "export", my_wxid, "dedb")
|
||||
outpath = os.path.join(gc.work_path, "export", my_wxid, "dedb")
|
||||
if not os.path.exists(outpath):
|
||||
os.makedirs(outpath)
|
||||
assert isinstance(outpath, str)
|
||||
@ -393,17 +428,22 @@ def get_export_dedb():
|
||||
return ReJson(2001, body=merge_save_path)
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/export_csv', methods=["GET", 'POST'])
|
||||
def get_export_csv():
|
||||
class ExportCsvRequest(BaseModel):
|
||||
wxid: str
|
||||
|
||||
|
||||
@rs_api.api_route('/export_csv', methods=["GET", 'POST'])
|
||||
def get_export_csv(request: ExportCsvRequest):
|
||||
"""
|
||||
导出csv
|
||||
:return:
|
||||
"""
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = get_conf(g.caf, my_wxid, "db_config")
|
||||
|
||||
wxid = request.json.get("wxid")
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = gc.get_conf(my_wxid, "db_config")
|
||||
|
||||
wxid = request.wxid
|
||||
# st_ed_time = request.json.get("datetime", [0, 0])
|
||||
if not wxid:
|
||||
return ReJson(1002, body=f"username is required: {wxid}")
|
||||
@ -413,7 +453,7 @@ def get_export_csv():
|
||||
# if not isinstance(start, int) or not isinstance(end, int) or start >= end:
|
||||
# return ReJson(1002, body=f"datetime is required: {st_ed_time}")
|
||||
|
||||
outpath = os.path.join(g.work_path, "export", my_wxid, "csv", wxid)
|
||||
outpath = os.path.join(gc.work_path, "export", my_wxid, "csv", wxid)
|
||||
if not os.path.exists(outpath):
|
||||
os.makedirs(outpath)
|
||||
|
||||
@ -424,21 +464,26 @@ def get_export_csv():
|
||||
return ReJson(2001, body=ret)
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/export_json', methods=["GET", 'POST'])
|
||||
def get_export_json():
|
||||
class ExportJsonRequest(BaseModel):
|
||||
wxid: str
|
||||
|
||||
|
||||
@rs_api.api_route('/export_json', methods=["GET", 'POST'])
|
||||
def get_export_json(request: ExportJsonRequest):
|
||||
"""
|
||||
导出json
|
||||
:return:
|
||||
"""
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = get_conf(g.caf, my_wxid, "db_config")
|
||||
|
||||
wxid = request.json.get("wxid")
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = gc.get_conf(my_wxid, "db_config")
|
||||
|
||||
wxid = request.wxid
|
||||
if not wxid:
|
||||
return ReJson(1002, body=f"username is required: {wxid}")
|
||||
|
||||
outpath = os.path.join(g.work_path, "export", my_wxid, "json", wxid)
|
||||
outpath = os.path.join(gc.work_path, "export", my_wxid, "json", wxid)
|
||||
if not os.path.exists(outpath):
|
||||
os.makedirs(outpath)
|
||||
|
||||
@ -449,21 +494,26 @@ def get_export_json():
|
||||
return ReJson(2001, body=ret)
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/export_html', methods=["GET", 'POST'])
|
||||
def get_export_html():
|
||||
class ExportHtmlRequest(BaseModel):
|
||||
wxid: str
|
||||
|
||||
|
||||
@rs_api.api_route('/export_html', methods=["GET", 'POST'])
|
||||
def get_export_html(request: ExportHtmlRequest):
|
||||
"""
|
||||
导出json
|
||||
:return:
|
||||
"""
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = get_conf(g.caf, my_wxid, "db_config")
|
||||
|
||||
wxid = request.json.get("wxid")
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = gc.get_conf(my_wxid, "db_config")
|
||||
|
||||
wxid = request.wxid
|
||||
if not wxid:
|
||||
return ReJson(1002, body=f"username is required: {wxid}")
|
||||
|
||||
html_outpath = os.path.join(g.work_path, "export", my_wxid, "html")
|
||||
html_outpath = os.path.join(gc.work_path, "export", my_wxid, "html")
|
||||
if not os.path.exists(html_outpath):
|
||||
os.makedirs(html_outpath)
|
||||
assert isinstance(html_outpath, str)
|
||||
@ -474,7 +524,6 @@ def get_export_html():
|
||||
web_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "ui", "web")
|
||||
shutil.copytree(web_path, outpath)
|
||||
|
||||
|
||||
code, ret = export_html(wxid, outpath, db_config, my_wxid=my_wxid)
|
||||
|
||||
if code:
|
||||
@ -486,69 +535,75 @@ def get_export_html():
|
||||
# end 导出聊天记录 *******************************************************************************************************
|
||||
|
||||
# start 聊天记录分析api **************************************************************************************************
|
||||
class DateCountRequest(BaseModel):
|
||||
wxid: str = ""
|
||||
start_time: int = 0
|
||||
end_time: int = 0
|
||||
time_format: str = "%Y-%m-%d"
|
||||
|
||||
@rs_api.route('/api/rs/date_count', methods=["GET", 'POST'])
|
||||
def get_date_count():
|
||||
|
||||
@rs_api.api_route('/date_count', methods=["GET", 'POST'])
|
||||
def get_date_count(request: DateCountRequest):
|
||||
"""
|
||||
获取日期统计
|
||||
:return:
|
||||
"""
|
||||
if request.method not in ["GET", "POST"]:
|
||||
return ReJson(1003, msg="Unsupported method")
|
||||
rq_data = request.json if request.method == "POST" else request.args
|
||||
word = rq_data.get("wxid", "")
|
||||
start_time = rq_data.get("start_time", 0)
|
||||
end_time = rq_data.get("end_time", 0)
|
||||
time_format = rq_data.get("time_format", "%Y-%m-%d")
|
||||
wxid = request.wxid
|
||||
start_time = request.start_time
|
||||
end_time = request.end_time
|
||||
time_format = request.time_format
|
||||
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = get_conf(g.caf, my_wxid, "db_config")
|
||||
db_config = gc.get_conf(my_wxid, "db_config")
|
||||
db = DBHandler(db_config, my_wxid=my_wxid)
|
||||
date_count = db.get_date_count(wxid=word, start_time=start_time, end_time=end_time, time_format=time_format)
|
||||
date_count = db.get_date_count(wxid=wxid, start_time=start_time, end_time=end_time, time_format=time_format)
|
||||
return ReJson(0, date_count)
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/top_talker_count', methods=["GET", 'POST'])
|
||||
def get_top_talker_count():
|
||||
class TopTalkerCountRequest(BaseModel):
|
||||
top: int = 10
|
||||
start_time: int = 0
|
||||
end_time: int = 0
|
||||
|
||||
|
||||
@rs_api.api_route('/top_talker_count', methods=["GET", 'POST'])
|
||||
def get_top_talker_count(request: TopTalkerCountRequest):
|
||||
"""
|
||||
获取最多聊天的人
|
||||
:return:
|
||||
"""
|
||||
if request.method not in ["GET", "POST"]:
|
||||
return ReJson(1003, msg="Unsupported method")
|
||||
rq_data = request.json if request.method == "POST" else request.args
|
||||
top = rq_data.get("top", 10)
|
||||
start_time = rq_data.get("start_time", 0)
|
||||
end_time = rq_data.get("end_time", 0)
|
||||
top = request.top
|
||||
start_time = request.start_time
|
||||
end_time = request.end_time
|
||||
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = get_conf(g.caf, my_wxid, "db_config")
|
||||
db_config = gc.get_conf(my_wxid, "db_config")
|
||||
date_count = DBHandler(db_config, my_wxid=my_wxid).get_top_talker_count(top=top, start_time=start_time,
|
||||
end_time=end_time)
|
||||
return ReJson(0, date_count)
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/wordcloud', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def wordcloud():
|
||||
if request.method not in ["GET", "POST"]:
|
||||
return ReJson(1003, msg="Unsupported method")
|
||||
rq_data = request.json if request.method == "POST" else request.args
|
||||
class WordCloudRequest(BaseModel):
|
||||
target: str = "signature"
|
||||
|
||||
|
||||
@rs_api.api_route('/wordcloud', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def get_wordcloud(request: WordCloudRequest):
|
||||
try:
|
||||
import jieba
|
||||
except ImportError:
|
||||
return ReJson(9999, body="jieba is required")
|
||||
|
||||
target = rq_data.get("target", "")
|
||||
target = request.target
|
||||
if not target:
|
||||
return ReJson(1002, body="target is required")
|
||||
|
||||
my_wxid = get_conf(g.caf, g.at, "last")
|
||||
my_wxid = gc.get_conf(gc.at, "last")
|
||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||
db_config = get_conf(g.caf, my_wxid, "db_config")
|
||||
db_config = gc.get_conf(my_wxid, "db_config")
|
||||
db = DBHandler(db_config, my_wxid=my_wxid)
|
||||
|
||||
if target == "signature":
|
||||
@ -583,7 +638,7 @@ def wordcloud():
|
||||
# end 聊天记录分析api ****************************************************************************************************
|
||||
|
||||
# 关于、帮助、设置 *******************************************************************************************************
|
||||
@rs_api.route('/api/rs/check_update', methods=["GET", 'POST'])
|
||||
@rs_api.api_route('/check_update', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def check_update():
|
||||
"""
|
||||
@ -609,7 +664,7 @@ def check_update():
|
||||
return ReJson(9999, msg=str(e))
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/version', methods=["GET", 'POST'])
|
||||
@rs_api.api_route('/version', methods=["GET", "POST"])
|
||||
@error9999
|
||||
def version():
|
||||
"""
|
||||
@ -619,7 +674,7 @@ def version():
|
||||
return ReJson(0, pywxdump.__version__)
|
||||
|
||||
|
||||
@rs_api.route('/api/rs/get_readme', methods=["GET", 'POST'])
|
||||
@rs_api.api_route('/get_readme', methods=["GET", 'POST'])
|
||||
@error9999
|
||||
def get_readme():
|
||||
"""
|
||||
@ -638,9 +693,3 @@ def get_readme():
|
||||
|
||||
|
||||
# END 关于、帮助、设置 ***************************************************************************************************
|
||||
|
||||
|
||||
@rs_api.route('/')
|
||||
@error9999
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
@ -16,8 +16,122 @@ from .rjson import ReJson
|
||||
from functools import wraps
|
||||
import logging
|
||||
|
||||
rs_loger = logging.getLogger("rs_api")
|
||||
ls_loger = logging.getLogger("ls_api")
|
||||
server_loger = logging.getLogger("server")
|
||||
rs_loger = server_loger
|
||||
ls_loger = server_loger
|
||||
|
||||
|
||||
class ConfData(object):
|
||||
_instances = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instances:
|
||||
return cls._instances
|
||||
cls._instances = object.__new__(cls)
|
||||
return cls._instances
|
||||
|
||||
def __init__(self):
|
||||
self._work_path = None
|
||||
self.conf_file = None
|
||||
self.auto_setting = None
|
||||
|
||||
self.is_init = False
|
||||
|
||||
self.conf = {}
|
||||
|
||||
self.init()
|
||||
|
||||
@property
|
||||
def cf(self):
|
||||
if not self.is_init:
|
||||
self.init()
|
||||
return self.conf_file
|
||||
|
||||
@property
|
||||
def work_path(self):
|
||||
if not self.is_init:
|
||||
self.init()
|
||||
return self._work_path
|
||||
|
||||
@property
|
||||
def at(self):
|
||||
if not self.is_init:
|
||||
self.init()
|
||||
return self.auto_setting
|
||||
|
||||
def init(self):
|
||||
self.is_init = False
|
||||
|
||||
work_path = os.getenv("PYWXDUMP_WORK_PATH")
|
||||
conf_file = os.getenv("PYWXDUMP_CONF_FILE")
|
||||
auto_setting = os.getenv("PYWXDUMP_AUTO_SETTING")
|
||||
|
||||
if work_path is None or conf_file is None or auto_setting is None:
|
||||
return False
|
||||
|
||||
self._work_path = work_path
|
||||
self.conf_file = conf_file
|
||||
self.auto_setting = auto_setting
|
||||
if not os.path.exists(self.conf_file):
|
||||
self.set_conf(self.auto_setting, "last", "")
|
||||
self.is_init = True
|
||||
self.read_conf()
|
||||
return True
|
||||
|
||||
def read_conf(self):
|
||||
if not self.is_init:
|
||||
self.init()
|
||||
try:
|
||||
with open(self.conf_file, 'r') as f:
|
||||
conf = json.load(f)
|
||||
self.conf = conf
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
logging.error(f"Session file not found: {self.conf_file}")
|
||||
return False
|
||||
except json.JSONDecodeError as e:
|
||||
logging.error(f"Error decoding JSON file: {e}")
|
||||
return False
|
||||
|
||||
def write_conf(self):
|
||||
if not self.is_init:
|
||||
self.init()
|
||||
try:
|
||||
with open(self.conf_file, 'w') as f:
|
||||
json.dump(self.conf, f, indent=4, ensure_ascii=False)
|
||||
return True
|
||||
except Exception as e:
|
||||
logging.error(f"Error writing to file: {e}")
|
||||
return False
|
||||
|
||||
def set_conf(self, wxid, arg, value):
|
||||
if not self.is_init:
|
||||
self.init()
|
||||
if wxid not in self.conf:
|
||||
self.conf[wxid] = {}
|
||||
if not isinstance(self.conf[wxid], dict):
|
||||
self.conf[wxid] = {}
|
||||
self.conf[wxid][arg] = value
|
||||
self.write_conf()
|
||||
|
||||
def get_conf(self, wxid, arg):
|
||||
if not self.is_init:
|
||||
self.init()
|
||||
return self.conf.get(wxid, {}).get(arg, None)
|
||||
|
||||
def get_local_wxids(self):
|
||||
if not self.is_init:
|
||||
self.init()
|
||||
return list(self.conf.keys())
|
||||
|
||||
def get_db_config(self):
|
||||
if not self.is_init:
|
||||
self.init()
|
||||
my_wxid = self.get_conf(self.at, "last")
|
||||
return self.get_conf(my_wxid, "db_config")
|
||||
|
||||
|
||||
gc: ConfData = ConfData()
|
||||
|
||||
|
||||
def get_conf_local_wxid(conf_file):
|
||||
@ -83,6 +197,16 @@ def set_conf(conf_file, wxid, arg, value):
|
||||
return True
|
||||
|
||||
|
||||
def is_port_in_use(_host, _port):
|
||||
import socket
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
try:
|
||||
s.bind((_host, _port))
|
||||
except socket.error:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def validate_title(title):
|
||||
"""
|
||||
校验文件名是否合法
|
||||
@ -106,6 +230,20 @@ def error9999(func):
|
||||
return wrapper
|
||||
|
||||
|
||||
def asyncError9999(func):
|
||||
@wraps(func)
|
||||
async def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return await func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
traceback_data = traceback.format_exc()
|
||||
rdata = f"{traceback_data}"
|
||||
# logging.error(rdata)
|
||||
return ReJson(9999, body=f"{str(e)}\n{rdata}", error=str(e))
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def gen_base64(path):
|
||||
# 获取文件名后缀
|
||||
extension = os.path.splitext(path)[1]
|
||||
|
@ -6,8 +6,8 @@
|
||||
# Date: 2023/10/14
|
||||
# -------------------------------------------------------------------------------
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from pywxdump import *
|
||||
import pywxdump
|
||||
@ -289,7 +289,7 @@ class MainShowChatRecords(BaseSubMainClass):
|
||||
print("[-] 输入数据库路径不存在")
|
||||
return
|
||||
|
||||
start_falsk(merge_path=merge_path, wx_path=args.wx_path, key="", my_wxid=args.my_wxid, online=online)
|
||||
start_server(merge_path=merge_path, wx_path=args.wx_path, key="", my_wxid=args.my_wxid, online=online)
|
||||
|
||||
|
||||
class MainExportChatRecords(BaseSubMainClass):
|
||||
@ -327,7 +327,7 @@ class MainUi(BaseSubMainClass):
|
||||
parser.add_argument("-p", '--port', metavar="", type=int, help="(可选)端口号", default=5000)
|
||||
parser.add_argument("--online", help="(可选)是否在线查看(局域网查看)", default=False, action='store_true')
|
||||
parser.add_argument("--debug", help="(可选)是否开启debug模式", default=False, action='store_true')
|
||||
parser.add_argument("--noOpenBrowser", dest='isOpenBrowser', action='store_false', default=True,
|
||||
parser.add_argument("--noOpenBrowser", dest='isOpenBrowser', default=True, action='store_false',
|
||||
help="(可选)用于禁用自动打开浏览器")
|
||||
return parser
|
||||
|
||||
@ -339,7 +339,7 @@ class MainUi(BaseSubMainClass):
|
||||
debug = args.debug
|
||||
isopenBrowser = args.isOpenBrowser
|
||||
|
||||
start_falsk(port=port, online=online, debug=debug, isopenBrowser=isopenBrowser)
|
||||
start_server(port=port, online=online, debug=debug, isopenBrowser=isopenBrowser)
|
||||
|
||||
|
||||
class MainApi(BaseSubMainClass):
|
||||
@ -360,7 +360,7 @@ class MainApi(BaseSubMainClass):
|
||||
port = args.port
|
||||
debug = args.debug
|
||||
|
||||
start_falsk(port=port, online=online, debug=debug, isopenBrowser=False)
|
||||
start_server(port=port, online=online, debug=debug, isopenBrowser=False)
|
||||
|
||||
|
||||
def console_run():
|
||||
|
@ -242,7 +242,7 @@ class MsgHandler(DatabaseBase):
|
||||
voicelength = int(voicelength) / 1000
|
||||
voicelength = f"{voicelength:.2f}"
|
||||
msg = f"语音时长:{voicelength}秒\n翻译结果:{transtext}" if transtext else f"语音时长:{voicelength}秒"
|
||||
src = os.path.join("audio", f"{StrTalker}",
|
||||
src = os.path.join(f"{StrTalker}",
|
||||
f"{CreateTime.replace(':', '-').replace(' ', '_')}_{IsSender}_{MsgSvrID}.wav")
|
||||
elif type_id == (43, 0): # 视频
|
||||
DictExtra = get_BytesExtra(BytesExtra)
|
||||
|
@ -96,8 +96,11 @@ class DatabaseBase(DatabaseSingletonBase):
|
||||
def __get_existed_tables(self):
|
||||
sql = "SELECT tbl_name FROM sqlite_master WHERE type = 'table' and tbl_name!='sqlite_sequence';"
|
||||
existing_tables = self.execute(sql)
|
||||
self.existed_tables = [row[0].lower() for row in existing_tables]
|
||||
return self.existed_tables
|
||||
if existing_tables:
|
||||
self.existed_tables = [row[0].lower() for row in existing_tables]
|
||||
return self.existed_tables
|
||||
else:
|
||||
return None
|
||||
|
||||
def tables_exist(self, required_tables: str or list):
|
||||
"""
|
||||
|
@ -252,18 +252,19 @@ def xml2dict(xml_string):
|
||||
return parse_xml(root)
|
||||
|
||||
|
||||
def download_file(url, save_path=None):
|
||||
def download_file(url, save_path=None, proxies=None):
|
||||
"""
|
||||
下载文件
|
||||
:param url: 文件下载地址
|
||||
:param save_path: 保存路径
|
||||
:param proxies: requests 代理
|
||||
:return: 保存路径
|
||||
"""
|
||||
headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Linux; Android 10; Redmi K40 Pro) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Mobile Safari/537.36"
|
||||
|
||||
}
|
||||
r = requests.get(url, headers=headers)
|
||||
r = requests.get(url, headers=headers, proxies=proxies)
|
||||
if r.status_code != 200:
|
||||
return None
|
||||
data = r.content
|
||||
|
@ -1,143 +0,0 @@
|
||||
# -*- coding: utf-8 -*-#
|
||||
# -------------------------------------------------------------------------------
|
||||
# Name: server.py
|
||||
# Description:
|
||||
# Author: xaoyaoo
|
||||
# Date: 2024/01/04
|
||||
# -------------------------------------------------------------------------------
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
|
||||
server_loger = logging.getLogger("server")
|
||||
|
||||
|
||||
def is_port_in_use(_host, _port):
|
||||
import socket
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
try:
|
||||
s.bind((_host, _port))
|
||||
except socket.error:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def start_falsk(merge_path="", wx_path="", key="", my_wxid="", port=5000, online=False, debug=False,
|
||||
isopenBrowser=True, loger_handler=None):
|
||||
"""
|
||||
启动flask
|
||||
:param merge_path: 合并后的数据库路径
|
||||
:param wx_path: 微信文件夹的路径(用于显示图片)
|
||||
:param key: 密钥
|
||||
:param my_wxid: 微信账号(本人微信id)
|
||||
:param port: 端口号
|
||||
:param online: 是否在线查看(局域网查看)
|
||||
:param debug: 是否开启debug模式
|
||||
:param isopenBrowser: 是否自动打开浏览器
|
||||
:return:
|
||||
"""
|
||||
work_path = os.path.join(os.getcwd(), "wxdump_work") # 临时文件夹,用于存放图片等
|
||||
if not os.path.exists(work_path):
|
||||
os.makedirs(work_path)
|
||||
server_loger.info(f"[+] 创建临时文件夹:{work_path}")
|
||||
print(f"[+] 创建临时文件夹:{work_path}")
|
||||
|
||||
conf_auto_file = os.path.join(work_path, "conf_auto.json") # 用于存放各种基础信息
|
||||
at = "auto_setting"
|
||||
|
||||
from flask import Flask, g
|
||||
from flask_cors import CORS
|
||||
from pywxdump.api import rs_api, ls_api, get_conf, set_conf
|
||||
|
||||
# 检查端口是否被占用
|
||||
if online:
|
||||
host = '0.0.0.0'
|
||||
else:
|
||||
host = "127.0.0.1"
|
||||
|
||||
app = Flask(__name__, template_folder='./ui/web', static_folder='./ui/web/assets/', static_url_path='/assets/')
|
||||
with app.app_context():
|
||||
# 设置超时时间为 1000 秒
|
||||
app.config['TIMEOUT'] = 1000
|
||||
app.secret_key = 'secret_key'
|
||||
|
||||
app.logger.setLevel(logging.WARNING)
|
||||
if loger_handler:
|
||||
app.logger.addHandler(loger_handler)
|
||||
# 获取 Werkzeug 的日志记录器
|
||||
werkzeug_logger = logging.getLogger('werkzeug')
|
||||
# 将自定义格式器应用到 Werkzeug 的日志记录器
|
||||
werkzeug_logger.addHandler(loger_handler)
|
||||
werkzeug_logger.setLevel(logging.DEBUG)
|
||||
|
||||
CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True) # 允许所有域名跨域
|
||||
|
||||
@app.after_request # 请求后的处理 用于解决部分用户浏览器不支持flask以及vue的js文件返回问题
|
||||
def changeHeader(response):
|
||||
disposition = response.get_wsgi_headers('environ').get(
|
||||
'Content-Disposition') or '' # 获取返回头文件名描述,如'inline; filename=index.562b9b5a.js'
|
||||
if disposition.rfind('.js') == len(disposition) - 3:
|
||||
response.mimetype = 'application/javascript'
|
||||
return response
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
g.work_path = work_path # 临时文件夹,用于存放图片等-新版本
|
||||
g.caf = conf_auto_file # 用于存放各种基础信息-新版本
|
||||
g.at = at # 用于默认设置-新版本
|
||||
|
||||
if merge_path:
|
||||
set_conf(conf_auto_file, at, "merge_path", merge_path)
|
||||
db_config = {
|
||||
"key": "merge_all",
|
||||
"type": "sqlite",
|
||||
"path": "D:\\_code\\py_code\\pywxdumpProject\\z_test\\wxdump_work\\wxid_zh12s67kxsqs22\\merge_all.db"
|
||||
}
|
||||
set_conf(conf_auto_file, at, "db_config", db_config)
|
||||
if wx_path: set_conf(conf_auto_file, at, "wx_path", wx_path)
|
||||
if key: set_conf(conf_auto_file, at, "key", key)
|
||||
if my_wxid: set_conf(conf_auto_file, at, "my_wxid", my_wxid)
|
||||
if not os.path.exists(conf_auto_file):
|
||||
set_conf(conf_auto_file, at, "last", my_wxid)
|
||||
|
||||
app.register_blueprint(rs_api)
|
||||
app.register_blueprint(ls_api)
|
||||
|
||||
if isopenBrowser:
|
||||
try:
|
||||
# 自动打开浏览器
|
||||
url = f"http://127.0.0.1:{port}/"
|
||||
# 根据操作系统使用不同的命令打开默认浏览器
|
||||
if sys.platform.startswith('darwin'): # macOS
|
||||
subprocess.call(['open', url])
|
||||
elif sys.platform.startswith('win'): # Windows
|
||||
subprocess.call(['start', url], shell=True)
|
||||
elif sys.platform.startswith('linux'): # Linux
|
||||
subprocess.call(['xdg-open', url])
|
||||
else:
|
||||
server_loger.error(f"Unsupported platform, can't open browser automatically.", exc_info=True)
|
||||
print("Unsupported platform, can't open browser automatically.")
|
||||
except Exception as e:
|
||||
server_loger.error(f"自动打开浏览器失败:{e}", exc_info=True)
|
||||
|
||||
if is_port_in_use(host, port):
|
||||
server_loger.error(f"Port {port} is already in use. Choose a different port.")
|
||||
print(f"Port {port} is already in use. Choose a different port.")
|
||||
input("Press Enter to exit...")
|
||||
else:
|
||||
time.sleep(1)
|
||||
server_loger.info(f"启动flask服务,host:port:{host}:{port}")
|
||||
print("[+] 请使用浏览器访问 http://127.0.0.1:5000/ 查看聊天记录")
|
||||
app.run(host=host, port=port, debug=debug, threaded=False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
merge_path = r"****.db"
|
||||
|
||||
wx_path = r"****"
|
||||
my_wxid = "****"
|
||||
|
||||
start_falsk(merge_path=merge_path, wx_path=wx_path, my_wxid=my_wxid,
|
||||
port=5000, online=False, debug=False, isopenBrowser=False)
|
@ -15,3 +15,7 @@ lz4
|
||||
lxml
|
||||
flask_cors
|
||||
pandas
|
||||
|
||||
fastapi
|
||||
uvicorn
|
||||
dotenv
|
Loading…
Reference in New Issue
Block a user