diff --git a/README.md b/README.md index b7b0861..b0ffbce 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@
更新日志(点击展开): +* 2023.11.16 增加聊天记录导出为html * 2023.11.15 添加test文件,添加自动构建可执行文件的脚本,添加版本描述 * 2023.11.15 [v2.2.5变化较大]重构解密脚本的返回值,重构命令行参数 * 2023.11.15 修复无法获取wxid的bug @@ -48,7 +49,7 @@ ## 1. 项目简介 -PyWxDump可用于:获取用户个人信息(昵称/账号/手机/邮箱/数据库密钥(用来解密聊天记录));数据库读取、解密脚本;聊天记录查看工具。 +PyWxDump可用于:获取用户个人信息(昵称/账号/手机/邮箱/数据库密钥(用来解密聊天记录));数据库读取、解密脚本;聊天记录查看导出工具。 支持多账户信息获取,支持所有微信版本。 @@ -66,6 +67,7 @@ PyWxDump可用于:获取用户个人信息(昵称/账号/手机/邮箱/数据 * (6)提供数据库部分字段说明 * (7)支持微信多开场景,获取多用户信息等 * (8)微信需要登录状态才能获取数据库密钥 +* (9)支持导出聊天记录为html **版本差异** @@ -119,6 +121,7 @@ PyWxDump [PyWxDump](https://github.com/xaoyaoo/PyWxDump)是[SharpWxDump](https://github.com/AdminTest0/SharpWxDump) 的经过重构python语言版本,同时添加了一些新的功能。 +* 目前只在windows下测试过,linux下可能会存在问题。 * 如发现[version_list.json](pywxdump/version_list.json)缺失或错误, 请提交[issues](https://github.com/xaoyaoo/PyWxDump/issues). * 如发现bug或有改进意见, 请提交[issues](https://github.com/xaoyaoo/PyWxDump/issues). @@ -148,6 +151,9 @@ pip install pywxdump ### 1.2 从源码安装 +
+点击展开 + ```shell script pip install git+git://github.com/xaoyaoo/PyWxDump.git ``` @@ -159,6 +165,7 @@ git clone https://github.com/xaoyaoo/PyWxDump.git cd PyWxDump python -m pip install -U . ``` +
## 2. 使用 @@ -174,11 +181,15 @@ wxdump 模式 [参数] # db_path 获取微信文件夹路径 # decrypt 解密微信数据库 # dbshow 聊天记录查看[需要安装flask] +# export 聊天记录导出为html[需要安装flask] # all 获取微信信息,解密微信数据库,查看聊天记录 ``` *示例* +
+点击展开示例 + 以下是示例命令: ```shell script @@ -228,12 +239,28 @@ wxdump dbshow -h # -fs , --filestorage_path # (可选)文件夹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 #usage: main.py all [-h] #options: # -h, --help show this help message and exit ``` +
+ ### 2.2 python API 更多使用方法参考[tests](./tests)文件夹下的[test_*.py](./tests/)文件 @@ -302,6 +329,8 @@ else: # 三、免责声明(非常重要!!!!!!!) +本项目仅供学习交流使用,请勿用于非法用途,否则后果自负。 + 本项目仅允许在授权情况下对数据库进行备份,严禁用于非法目的,否则自行承担所有相关责任。使用该工具则代表默认同意该条款; 请勿利用本项目的相关技术从事非法测试,如因此产生的一切不良后果与项目作者无关。 diff --git a/pywxdump/command.py b/pywxdump/command.py index c5ac581..0adc664 100644 --- a/pywxdump/command.py +++ b/pywxdump/command.py @@ -169,6 +169,49 @@ class MainShowChatRecords(): 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(): def init_parses(self, parser): self.mode = "all" @@ -294,6 +337,11 @@ def console_run(): sb_dbshow = main_show_chat_records.init_parses(subparsers) 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' 子命令解析器 main_all = MainAll() sb_all = main_all.init_parses(subparsers) diff --git a/pywxdump/show_chat/main_window.py b/pywxdump/show_chat/main_window.py index ef9afda..92b0478 100644 --- a/pywxdump/show_chat/main_window.py +++ b/pywxdump/show_chat/main_window.py @@ -13,6 +13,8 @@ import time import hashlib 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): m = hashlib.md5() @@ -177,18 +179,49 @@ def load_chat_records(selected_talker, start_index, page_size, user_list, MSG_AL 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.debug = False +# 主页 - 显示用户列表 @app_show_chat.route('/') def index(): g.USER_LIST = get_user_list(g.MSG_ALL_db_path, g.MicroMsg_db_path) return render_template("index.html", users=g.USER_LIST) +# 获取聊天记录 @app_show_chat.route('/get_chat_data', methods=["GET", 'POST']) def get_chat_data(): username = request.args.get("username", "") @@ -208,3 +241,26 @@ def get_chat_data(): return render_template("chat.html", msgs=data) else: 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" diff --git a/pywxdump/show_chat/templates/chat.html b/pywxdump/show_chat/templates/chat.html index 5d2c1bc..cb2861d 100644 --- a/pywxdump/show_chat/templates/chat.html +++ b/pywxdump/show_chat/templates/chat.html @@ -4,6 +4,11 @@ chat + diff --git a/pywxdump/show_chat/templates/index.html b/pywxdump/show_chat/templates/index.html index 6edd0c1..dcc56b5 100644 --- a/pywxdump/show_chat/templates/index.html +++ b/pywxdump/show_chat/templates/index.html @@ -79,7 +79,7 @@
- +
@@ -184,6 +184,13 @@ request_function(requestUrl); } }); + + // 导出按钮点击事件 + document.getElementById('btn_export').addEventListener('click', function () { + var requestUrl = '/export_chat_data?username=' + encodeURIComponent(globalUsername); + window.open(requestUrl); + }); + diff --git a/setup.py b/setup.py index 939f1ef..eee8f94 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,20 @@ from setuptools import setup, find_packages with open("README.md", "r", encoding="utf-8") as fh: 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( name="pywxdump", author="xaoyaoo", @@ -33,17 +46,7 @@ setup( "Operating System :: OS Independent", ], python_requires='>=3.6, <4', - install_requires=[ - "psutil", - "pycryptodomex", - "pywin32", - "pymem", - "silk-python", - "pyaudio", - "requests", - "pillow", - "pyahocorasick" - ], + install_requires=install_requires, entry_points={ 'console_scripts': [ 'wxdump = pywxdump.command:console_run',