增加聊天记录导出为html

This commit is contained in:
xaoyo 2023-11-16 18:55:45 +08:00
parent 447dec78c9
commit 21fe2bbcbd
6 changed files with 163 additions and 15 deletions

View File

@ -16,6 +16,7 @@
<details> <details>
<summary><strong>更新日志(点击展开)</strong></summary> <summary><strong>更新日志(点击展开)</strong></summary>
* 2023.11.16 增加聊天记录导出为html
* 2023.11.15 添加test文件添加自动构建可执行文件的脚本,添加版本描述 * 2023.11.15 添加test文件添加自动构建可执行文件的脚本,添加版本描述
* 2023.11.15 [v2.2.5变化较大]重构解密脚本的返回值,重构命令行参数 * 2023.11.15 [v2.2.5变化较大]重构解密脚本的返回值,重构命令行参数
* 2023.11.15 修复无法获取wxid的bug * 2023.11.15 修复无法获取wxid的bug
@ -48,7 +49,7 @@
## 1. 项目简介 ## 1. 项目简介
PyWxDump可用于获取用户个人信息(昵称/账号/手机/邮箱/数据库密钥(用来解密聊天记录));数据库读取、解密脚本;聊天记录查看工具。 PyWxDump可用于获取用户个人信息(昵称/账号/手机/邮箱/数据库密钥(用来解密聊天记录));数据库读取、解密脚本;聊天记录查看导出工具。
支持多账户信息获取,支持所有微信版本。 支持多账户信息获取,支持所有微信版本。
@ -66,6 +67,7 @@ PyWxDump可用于获取用户个人信息(昵称/账号/手机/邮箱/数据
* 6提供数据库部分字段说明 * 6提供数据库部分字段说明
* 7支持微信多开场景获取多用户信息等 * 7支持微信多开场景获取多用户信息等
* 8微信需要登录状态才能获取数据库密钥 * 8微信需要登录状态才能获取数据库密钥
* 9支持导出聊天记录为html
**版本差异** **版本差异**
@ -119,6 +121,7 @@ PyWxDump
[PyWxDump](https://github.com/xaoyaoo/PyWxDump)是[SharpWxDump](https://github.com/AdminTest0/SharpWxDump) [PyWxDump](https://github.com/xaoyaoo/PyWxDump)是[SharpWxDump](https://github.com/AdminTest0/SharpWxDump)
的经过重构python语言版本同时添加了一些新的功能。 的经过重构python语言版本同时添加了一些新的功能。
* 目前只在windows下测试过linux下可能会存在问题。
* 如发现[version_list.json](pywxdump/version_list.json)缺失或错误, * 如发现[version_list.json](pywxdump/version_list.json)缺失或错误,
请提交[issues](https://github.com/xaoyaoo/PyWxDump/issues). 请提交[issues](https://github.com/xaoyaoo/PyWxDump/issues).
* 如发现bug或有改进意见, 请提交[issues](https://github.com/xaoyaoo/PyWxDump/issues). * 如发现bug或有改进意见, 请提交[issues](https://github.com/xaoyaoo/PyWxDump/issues).
@ -148,6 +151,9 @@ pip install pywxdump
### 1.2 从源码安装 ### 1.2 从源码安装
<details>
<summary>点击展开</summary>
```shell script ```shell script
pip install git+git://github.com/xaoyaoo/PyWxDump.git pip install git+git://github.com/xaoyaoo/PyWxDump.git
``` ```
@ -159,6 +165,7 @@ git clone https://github.com/xaoyaoo/PyWxDump.git
cd PyWxDump cd PyWxDump
python -m pip install -U . python -m pip install -U .
``` ```
</details>
## 2. 使用 ## 2. 使用
@ -174,11 +181,15 @@ wxdump 模式 [参数]
# db_path 获取微信文件夹路径 # db_path 获取微信文件夹路径
# decrypt 解密微信数据库 # decrypt 解密微信数据库
# dbshow 聊天记录查看[需要安装flask] # dbshow 聊天记录查看[需要安装flask]
# export 聊天记录导出为html[需要安装flask]
# all 获取微信信息,解密微信数据库,查看聊天记录 # all 获取微信信息,解密微信数据库,查看聊天记录
``` ```
*示例* *示例*
<details>
<summary>点击展开示例</summary>
以下是示例命令: 以下是示例命令:
```shell script ```shell script
@ -228,12 +239,28 @@ wxdump dbshow -h
# -fs , --filestorage_path # -fs , --filestorage_path
# (可选)文件夹FileStorage的路径用于显示图片 # (可选)文件夹FileStorage的路径用于显示图片
wxdump export -h
#usage: wxdump export [-h] -u -o -msg -micro -media [-fs]
#options:
# -h, --help show this help message and exit
# -u , --username 微信账号
# -o , --outpath 导出路径
# -msg , --msg_path 解密后的 MSG.db 的路径
# -micro , --micro_path
# 解密后的 MicroMsg.db 的路径
# -media , --media_path
# 解密后的 MediaMSG.db 的路径
# -fs , --filestorage_path
# (可选)文件夹FileStorage的路径用于显示图片
wxdump all -h wxdump all -h
#usage: main.py all [-h] #usage: main.py all [-h]
#options: #options:
# -h, --help show this help message and exit # -h, --help show this help message and exit
``` ```
</details>
### 2.2 python API ### 2.2 python API
更多使用方法参考[tests](./tests)文件夹下的[test_*.py](./tests/)文件 更多使用方法参考[tests](./tests)文件夹下的[test_*.py](./tests/)文件
@ -302,6 +329,8 @@ else:
# 三、免责声明(非常重要!!!!!!!) # 三、免责声明(非常重要!!!!!!!)
本项目仅供学习交流使用,请勿用于非法用途,否则后果自负。
本项目仅允许在授权情况下对数据库进行备份,严禁用于非法目的,否则自行承担所有相关责任。使用该工具则代表默认同意该条款; 本项目仅允许在授权情况下对数据库进行备份,严禁用于非法目的,否则自行承担所有相关责任。使用该工具则代表默认同意该条款;
请勿利用本项目的相关技术从事非法测试,如因此产生的一切不良后果与项目作者无关。 请勿利用本项目的相关技术从事非法测试,如因此产生的一切不良后果与项目作者无关。

View File

@ -169,6 +169,49 @@ class MainShowChatRecords():
app.run(debug=False) app.run(debug=False)
class MainExportChatRecords():
def init_parses(self, parser):
self.mode = "export"
# 添加 'decrypt' 子命令解析器
sb_decrypt = parser.add_parser(self.mode, help="聊天记录导出为html")
sb_decrypt.add_argument("-u", "--username", type=str, help="微信账号", required=True, metavar="")
sb_decrypt.add_argument("-o", "--outpath", type=str, help="导出路径", required=True, metavar="")
sb_decrypt.add_argument("-msg", "--msg_path", type=str, help="解密后的 MSG.db 的路径", required=True,
metavar="")
sb_decrypt.add_argument("-micro", "--micro_path", type=str, help="解密后的 MicroMsg.db 的路径", required=True,
metavar="")
sb_decrypt.add_argument("-media", "--media_path", type=str, help="解密后的 MediaMSG.db 的路径", required=True,
metavar="")
sb_decrypt.add_argument("-fs", "--filestorage_path", type=str,
help="(可选)文件夹FileStorage的路径用于显示图片", required=False,
metavar="")
return sb_decrypt
def run(self, args):
# 从命令行参数获取值
try:
from flask import Flask, request, jsonify, render_template, g
import logging
from .show_chat.main_window import app_show_chat, get_user_list, export
except Exception as e:
print(e)
print("[-] 请安装flask( pip install flask )")
return
if not os.path.exists(args.msg_path) or not os.path.exists(args.micro_path) or not os.path.exists(
args.media_path):
print(os.path.exists(args.msg_path), os.path.exists(args.micro_path), os.path.exists(args.media_path))
print("[-] 输入数据库路径不存在")
return
if not os.path.exists(args.outpath):
os.makedirs(args.outpath)
print(f"[+] 创建输出文件夹:{args.outpath}")
export(args.username, args.outpath, args.msg_path, args.micro_path, args.media_path, args.filestorage_path)
print(f"[+] 导出成功{args.outpath}")
class MainAll(): class MainAll():
def init_parses(self, parser): def init_parses(self, parser):
self.mode = "all" self.mode = "all"
@ -294,6 +337,11 @@ def console_run():
sb_dbshow = main_show_chat_records.init_parses(subparsers) sb_dbshow = main_show_chat_records.init_parses(subparsers)
modes[main_show_chat_records.mode] = main_show_chat_records modes[main_show_chat_records.mode] = main_show_chat_records
# 添加 'export' 子命令解析器
main_export_chat_records = MainExportChatRecords()
sb_export = main_export_chat_records.init_parses(subparsers)
modes[main_export_chat_records.mode] = main_export_chat_records
# 添加 'all' 子命令解析器 # 添加 'all' 子命令解析器
main_all = MainAll() main_all = MainAll()
sb_all = main_all.init_parses(subparsers) sb_all = main_all.init_parses(subparsers)

View File

@ -13,6 +13,8 @@ import time
import hashlib import hashlib
from pywxdump.analyse import read_img_dat, decompress_CompressContent, read_audio, parse_xml_string from pywxdump.analyse import read_img_dat, decompress_CompressContent, read_audio, parse_xml_string
from flask import Flask, request, render_template, g, Blueprint
def get_md5(s): def get_md5(s):
m = hashlib.md5() m = hashlib.md5()
@ -177,18 +179,49 @@ def load_chat_records(selected_talker, start_index, page_size, user_list, MSG_AL
return data return data
from flask import Flask, request, render_template, g, Blueprint def export_html(user, outpath, MSG_ALL_db_path, MediaMSG_all_db_path, FileStorage_path, page_size=500):
name_save = user.get("remark", user.get("nickname", user.get("username", "")))
username = user.get("username", "")
chatCount = user.get("chat_count", 0)
for i in range(0, chatCount, page_size):
start_index = i
data = load_chat_records(username, start_index, page_size, user, MSG_ALL_db_path, MediaMSG_all_db_path,
FileStorage_path)
if len(data) == 0:
break
with open(f"{outpath}/{name_save}_{int(i / page_size)}.html", "w", encoding="utf-8") as f:
f.write(render_template("chat.html", msgs=data))
return True, f"导出成功{outpath}"
def export(username, outpath, MSG_ALL_db_path, MicroMsg_db_path, MediaMSG_all_db_path, FileStorage_path):
if not os.path.exists(outpath):
outpath = os.path.join(os.getcwd(), "export" + os.sep + username)
if not os.path.exists(outpath):
os.makedirs(outpath)
USER_LIST = get_user_list(MSG_ALL_db_path, MicroMsg_db_path)
user = list(filter(lambda x: x["username"] == username, USER_LIST))
if username and len(user) > 0:
user = user[0]
return export_html(user, outpath, MSG_ALL_db_path, MediaMSG_all_db_path, FileStorage_path)
app_show_chat = Blueprint('show_chat_main', __name__, template_folder='templates') app_show_chat = Blueprint('show_chat_main', __name__, template_folder='templates')
app_show_chat.debug = False app_show_chat.debug = False
# 主页 - 显示用户列表
@app_show_chat.route('/') @app_show_chat.route('/')
def index(): def index():
g.USER_LIST = get_user_list(g.MSG_ALL_db_path, g.MicroMsg_db_path) g.USER_LIST = get_user_list(g.MSG_ALL_db_path, g.MicroMsg_db_path)
return render_template("index.html", users=g.USER_LIST) return render_template("index.html", users=g.USER_LIST)
# 获取聊天记录
@app_show_chat.route('/get_chat_data', methods=["GET", 'POST']) @app_show_chat.route('/get_chat_data', methods=["GET", 'POST'])
def get_chat_data(): def get_chat_data():
username = request.args.get("username", "") username = request.args.get("username", "")
@ -208,3 +241,26 @@ def get_chat_data():
return render_template("chat.html", msgs=data) return render_template("chat.html", msgs=data)
else: else:
return "error" return "error"
# 聊天记录导出为html
@app_show_chat.route('/export_chat_data', methods=["GET", 'POST'])
def get_export():
username = request.args.get("username", "")
user = list(filter(lambda x: x["username"] == username, g.USER_LIST))
if username and len(user) > 0:
user = user[0]
n = f"{user.get('username', '')}_{user.get('nickname', '')}_{user.get('remark', '')}"
outpath = os.path.join(os.getcwd(), "export" + os.sep + n)
if not os.path.exists(outpath):
os.makedirs(outpath)
ret = export_html(user, outpath, g.MSG_ALL_db_path, g.MediaMSG_all_db_path, g.FileStorage_path, page_size=200)
if ret[0]:
return ret[1]
else:
return ret[1]
else:
return "error"

View File

@ -4,6 +4,11 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>chat</title> <title>chat</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<style>
img {
max-width: 400px;
}
</style>
</head> </head>
<body> <body>

View File

@ -79,7 +79,7 @@
</div> </div>
<div class="col-3" style="display: flex; justify-content: flex-end;"> <div class="col-3" style="display: flex; justify-content: flex-end;">
<button type="button" class="btn btn-primary">导出</button> <button id="btn_export" type="button" class="btn btn-primary">导出</button>
</div> </div>
</div> </div>
@ -184,6 +184,13 @@
request_function(requestUrl); request_function(requestUrl);
} }
}); });
// 导出按钮点击事件
document.getElementById('btn_export').addEventListener('click', function () {
var requestUrl = '/export_chat_data?username=' + encodeURIComponent(globalUsername);
window.open(requestUrl);
});
</script> </script>
</body> </body>

View File

@ -3,7 +3,20 @@ from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as fh: with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read() long_description = fh.read()
version = "2.2.6" version = "2.2.7"
install_requires = [
"psutil",
"pycryptodomex",
"pywin32",
"pymem",
"silk-python",
"pyaudio",
"requests",
"pillow",
"pyahocorasick"
]
setup( setup(
name="pywxdump", name="pywxdump",
author="xaoyaoo", author="xaoyaoo",
@ -33,17 +46,7 @@ setup(
"Operating System :: OS Independent", "Operating System :: OS Independent",
], ],
python_requires='>=3.6, <4', python_requires='>=3.6, <4',
install_requires=[ install_requires=install_requires,
"psutil",
"pycryptodomex",
"pywin32",
"pymem",
"silk-python",
"pyaudio",
"requests",
"pillow",
"pyahocorasick"
],
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'wxdump = pywxdump.command:console_run', 'wxdump = pywxdump.command:console_run',