该flask为fastapi,速度更快

This commit is contained in:
xaoyaoo 2024-08-17 11:51:57 +08:00
parent 81832a2a99
commit 0b937a2f11
11 changed files with 657 additions and 462 deletions

View File

@ -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"]

View File

@ -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"]

View File

@ -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)

View File

@ -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')

View File

@ -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]

View File

@ -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():

View File

@ -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)

View File

@ -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):
"""

View File

@ -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

View File

@ -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)

View File

@ -14,4 +14,8 @@ blackboxprotobuf
lz4
lxml
flask_cors
pandas
pandas
fastapi
uvicorn
dotenv