Compare commits

..

No commits in common. "master" and "v3.1.25" have entirely different histories.

31 changed files with 146 additions and 456 deletions

View File

@ -1,42 +0,0 @@
#on:
# push:
# branches: [master]
#name: Mirror GitHub Repos to Gitee
#jobs:
# run:
# name: Sync-GitHub-to-Gitee
# runs-on: ubuntu-latest
# steps:
# - name: Mirror the Github repos to Gitee.
# uses: Yikun/hub-mirror-action@master
# with:
# src: github/xaoyaoo
# dst: gitee/xaoyaoo
# dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
# dst_token: ${{ secrets.GITEE_TOKEN }}
# force_update: true
# src_account_type: org
# dst_account_type: user
# mappings: "dashboard=>dashboards"
# static_list: "trader"
# cache_path: /github/workspace/hub-mirror-cache
name: Hello World Action
on:
push:
branches: [ main ] # 触发条件:当主分支有新的推送时
jobs:
hello-job:
runs-on: ubuntu-latest # 运行环境:最新的 Ubuntu 系统
steps:
- name: Checkout Repository
uses: actions/checkout@v3 # 检出代码
- name: Print Hello Message
run: echo "Hello, world!" # 执行命令,打印消息
- name: Print Date
run: date # 执行命令,打印当前日期

View File

@ -38,7 +38,8 @@ QQ GROUP[276392799](https://s.xaoyo.top/gOLUDl) or [276392799](https://s.xaoy
#### 2.1 Core
* (1) Get the **base address offset** of WeChat nickname, WeChat account, WeChat phone number, WeChat email, and WeChat KEY
* (1) Get the **base address offset
** of WeChat nickname, WeChat account, WeChat phone number, WeChat email, and WeChat KEY
* (2) Get the WeChat nickname, WeChat account, WeChat phone number, WeChat email, WeChat KEY, WeChat original ID (wxid_******), and WeChat folder path of the currently logged-in WeChat
* (3) Decrypt WeChat database based on key
* (4) Combine multiple types of databases for unified viewing
@ -107,7 +108,7 @@ QQ GROUP[276392799](https://s.xaoyo.top/gOLUDl) or [276392799](https://s.xaoy
* If you want to modify the UI, clone the [wx_dump_web](https://github.com/xaoyaoo/wxdump_web) and modify it as needed (the UI is developed using VUE+ElementUI)
note】:
】:
* For obtaining the base address using cheat engine, refer to [CE obtaining base address.md](https://github.com/xaoyaoo/PyWxDump/tree/master/doc/CE获取基址.md)
(This method can be replaced by the `wxdump bias` command, and is only used for learning principles.)
@ -117,7 +118,9 @@ QQ GROUP[276392799](https://s.xaoyo.top/gOLUDl) or [276392799](https://s.xaoy
### 1. Purpose of use
* This project is only for learning and communication purposes, **please do not use it for illegal purposes**, **please do not use it for illegal purposes**, **please do not use it for illegal purposes**, otherwise the consequences will be borne by yourself.
* This project is only for learning and communication purposes, **please do not use it for illegal purposes**, **please
do not use it for illegal purposes**, **please do not use it for illegal purposes
**, otherwise the consequences will be borne by yourself.
* Users understand and agree that any violation of laws and regulations, infringement of the legitimate rights and interests of others, is unrelated to this project and its developers, and the consequences are borne by the user themselves.
### 2. Usage Period
@ -149,13 +152,45 @@ QQ GROUP[276392799](https://s.xaoyo.top/gOLUDl) or [276392799](https://s.xaoy
* Users are requested to carefully read and understand all contents of this disclaimer, and ensure that they strictly comply with relevant regulations when using this project.
# Ⅳ. Acknowledgments
# Ⅳ. 免责声明(非常重要!!!!!!!)
[![PyWxDump CONTRIBUTORS](https://contrib.rocks/image?repo=xaoyaoo/PyWxDump)](https://github.com/xaoyaoo/PyWxDump/graphs/contributors)
### 1. 使用目的
UI CONTRIBUTORS:
* 本项目仅供学习交流使用,**请勿用于非法用途****请勿用于非法用途****请勿用于非法用途**,否则后果自负。
* 用户理解并同意,任何违反法律法规、侵犯他人合法权益的行为,均与本项目及其开发者无关,后果由用户自行承担。
[![UI CONTRIBUTORS](https://contrib.rocks/image?repo=xaoyaoo/wxdump_web)](https://github.com/xaoyaoo/wxdump_web/graphs/contributors)
### 2. 使用期限
* 您应该在下载保存编译使用本项目的24小时内删除本项目的源代码和编译出的程序超出此期限的任何使用行为一概与本项目及其开发者无关。
### 3. 操作规范
* 本项目仅允许在授权情况下对数据库进行备份与查看,严禁用于非法目的,否则自行承担所有相关责任;用户如因违反此规定而引发的任何法律责任,将由用户自行承担,与本项目及其开发者无关。
* 严禁用于窃取他人隐私,严禁用于窃取他人隐私,严禁用于窃取他人隐私,否则自行承担所有相关责任。
* 严禁进行二次开发,严禁进行二次开发,严禁进行二次开发,否则自行承担所有相关责任。
### 4. 免责声明接受
* 下载、保存、进一步浏览源代码或者下载安装、编译使用本程序,表示你同意本警告,并承诺遵守它;
### 5. 禁止用于非法测试或渗透
* 禁止利用本项目的相关技术从事非法测试或渗透,禁止利用本项目的相关代码或相关技术从事任何非法工作,如因此产生的一切不良后果与本项目及其开发者无关。
* 任何因此产生的不良后果,包括但不限于数据泄露、系统瘫痪、侵犯隐私等,均与本项目及其开发者无关,责任由用户自行承担。
### 6. 免责声明修改
* 本免责声明可能根据项目运行情况和法律法规的变化进行修改和调整。用户应定期查阅本页面以获取最新版本的免责声明,使用本项目时应遵守最新版本的免责声明。
### 7. 其他
* 除本免责声明规定外,用户在使用本项目过程中应遵守相关的法律法规和道德规范。对于因用户违反相关规定而引发的任何纠纷或损失,本项目及其开发者不承担任何责任。
* 请用户慎重阅读并理解本免责声明的所有内容,确保在使用本项目时严格遵守相关规定。
# . Acknowledgments
[![PyWxDump CONTRIBUTORS](https://contrib.rocks/image?repo=xaoyaoo/PyWxDump)](https://github.com/xaoyaoo/PyWxDump/graphs/contributors)[![UI CONTRIBUTORS](https://contrib.rocks/image?repo=xaoyaoo/wxdump_web)](https://github.com/xaoyaoo/wxdump_web/graphs/contributors)
otherContributors:

View File

@ -1,5 +1,3 @@
## 注:本方法仅用于提供`pywxdump`的基址获取方式的原理。如果需要快捷获取基址,请执行`wxdump bias` + 各种参数。详细见命令说明或者可以使用图形界面启动wxdump.exe,选择实用工具,即可看到偏移获取,输入参数即可)
### 如何通过CE附加进程
1. 打开CE >> 选择左上角放大镜按钮 >> 选择微信进程 >> 选择附加到进程

View File

@ -1,117 +1,6 @@
## v3.1.46.(待发布)
## v3.1.25.(待发布)
- UPDATE CHANGELOG.md
## v3.1.45
- add wx 3.9.12.51
- UPDATE CHANGELOG.md
- fix gen_change_log.py
## v3.1.44
- fix #176
- fix #178
- update #178
## v3.1.43
- add 3.9.12.45
- add wx 3.9.12.37
## v3.1.42
- add wx 3.9.12.37
## v3.1.41, tag: v3.1.40, tag: v3.1.39
- 新增消息分类 (#162)
- fix 修改flask启动方式
- add wx 3.9.12.31
- UPDATE CHANGELOG.md
- Merge remote-tracking branch 'origin'
## v3.1.38
- fix
- 实时消息增加中文路径支持
- UPDATE CHANGELOG.md
## v3.1.37
- fix
- 完善收藏的类型转换体系
- fix tag查询结果去重
- modify log fmt
## v3.1.36
- fix #143
- UPDATE CHANGELOG.md
## v3.1.35
- fix
- 增加api文档说明
- UPDATE CHANGELOG.md
## v3.1.34
- add 注释
- fix CE获取基址.md
- UPDATE CHANGELOG.md
- UPDATE WXOFFS 3.9.12.17
## v3.1.33
- fix
- 群聊增加群成员显示
- 计划增加自动推送到gitee
- add wx 3.9.12.15
- UPDATE CHANGELOG.md
- update UserGuide.md
## v3.1.32
- 修改注释
- 群聊增加群成员显示
- M CE获取基址.md
- UPDATE CHANGELOG.md
## v3.1.31
- fix 联系人搜索bug
## v3.1.30
- fix
- add log to file
## v3.1.29
- fix
- fix 群聊list
## v3.1.28
- fix #125
- UPDATE CHANGELOG.md
## v3.1.27
- fix
- update test
- UPDATE CHANGELOG.md
- fix DIl load failed while importing pydantic_core:
## v3.1.26
- fix dbshow #124
## v3.1.25
- fix
- UPDATE CHANGELOG.md
-
## v3.1.24
@ -183,6 +72,7 @@
- 实时消息增加工具路径设置
- fix bug略微调整UI
- UPDATE CHANGELOG.md
- (backup/master) UPDATE CHANGELOG.md
## v3.1.13

View File

@ -29,7 +29,7 @@ pip install -U pywxdump
#### 1.2 从源码安装(安装最新版)
```shell script
pip install -U git+git://github.com/xaoyaoo/PyWxDump.git # 该方法无法安装网页图形界面会导致浏览器显示页面无法打开显示404
pip install -U git+git://github.com/xaoyaoo/PyWxDump.git # 该方法无法安装网页图形界面
```

View File

@ -227,33 +227,23 @@ FTS 这一前缀了——这代表的是搜索时所需的索引。
| 1 | 0 | 文本 |
| 3 | 0 | 图片 |
| 34 | 0 | 语音 |
| 37 | 0 | 打招呼, 加好友的时候输入的 `我是某某某` 这一句话 |
| 42 | 0 | 向别人推荐自己的好友 |
| 43 | 0 | 视频 |
| 47 | 0 | 动画表情(第三方开发的表情包) |
| 48 | 0 | 地图定位 |
| 49 | 1 | 类似文字消息而不一样的消息目前只见到一个阿里云盘的邀请注册是这样的还有飞书日程。估计和57子类的情况一样 |
| 49 | 4 | 分享 Bilibili 视频 |
| 49 | 1 | 类似文字消息而不一样的消息目前只见到一个阿里云盘的邀请注册是这样的。估计和57子类的情况一样 |
| 49 | 5 | 卡片式链接CompressContent 中有标题、简介等BytesExtra 中有本地缓存的封面路径 |
| 49 | 6 | 文件CompressContent 中有文件名和下载链接但不会读BytesExtra 中有本地保存的路径 |
| 49 | 8 | 用户上传的 GIF 表情CompressContent 中有CDN链接不过似乎不能直接访问下载 |
| 49 | 19 | 合并转发的聊天记录CompressContent 中有详细聊天记录BytesExtra 中有图片视频等的缓存 |
| 49 | 33/36 | 分享的小程序CompressContent 中有卡片信息BytesExtra 中有封面缓存位置 |
| 49 | 50 | 微视频 |
| 49 | 51 | 分享朋友圈动态 |
| 49 | 53 | 接龙 |
| 49 | 57 | 带有引用的文本消息(这种类型下 StrContent 为空,发送和引用的内容均在 CompressContent 中) |
| 49 | 63 | 视频号直播或直播回放等 |
| 49 | 76 | 分享歌曲 |
| 49 | 87 | 群公告 |
| 49 | 88 | 视频号直播或直播回放等 |
| 49 | 2000 | 转账消息(包括发出、接收、主动退还) |
| 49 | 2003 | 赠送红包封面 |
| 50 | 0 | 语音通话 |
| 65 | 0 | 朋友推荐消息 |
| 10000 | 0 | 系统通知(居中出现的那种灰色文字) |
| 10000 | 4 | 拍一拍 |
| 10000 | 8000 | 系统通知(特别包含你邀请别人加入群聊) |
## 更多内容查看:
https://blog.csdn.net/weixin_44495599/article/details/130030359
https://blog.csdn.net/weixin_44495599/article/details/130030359

View File

@ -411,47 +411,5 @@
93700888,
0,
93702352
],
"3.9.12.15": [
93813544,
93814880,
93813352,
0,
93814816
],
"3.9.12.17": [
93834984,
93836320,
93834792,
0,
93836256
],
"3.9.12.31": [
94516904,
94518240,
94516712,
0,
94518176
],
"3.9.12.37": [
94520808,
94522144,
94522146,
0,
94522080
],
"3.9.12.45": [
94503784,
94505120,
94503592,
0,
94505056
],
"3.9.12.51": [
94555176,
94556512,
94554984,
0,
94556448
]
}

View File

@ -5,7 +5,7 @@
# Author: xaoyaoo
# Date: 2023/10/14
# -------------------------------------------------------------------------------
__version__ = "3.1.45"
__version__ = "3.1.25"
import os, json
@ -31,4 +31,5 @@ from .api.export import export_html, export_csv, export_json
__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",
"DBHandler", "MsgHandler", "MicroHandler", "MediaHandler", "OpenIMContactHandler", "FavoriteHandler",
"PublicMsgHandler", "start_server", "WX_OFFS", "WX_OFFS_PATH", "__version__"]
"PublicMsgHandler",
"start_server", "WX_OFFS", "WX_OFFS_PATH", "__version__"]

View File

@ -8,6 +8,7 @@
import sqlite3
import time
from collections import Counter
import pandas as pd
from pywxdump.db.utils import xml2dict
from pywxdump.db import dbMSG
@ -18,7 +19,6 @@ def date_chat_count(chat_data, interval="W"):
:param chat_data: 聊天数据 json {"CreateTime":时间,"Type":消息类型,"SubType":消息子类型,"StrContent":消息内容,"StrTalker":聊天对象,"IsSender":是否发送者}
:param interval: 时间间隔 可选值daymonthyearweek
"""
import pandas as pd
chat_data = pd.DataFrame(chat_data)
chat_data["CreateTime"] = pd.to_datetime(chat_data["CreateTime"])
chat_data["AdjustedTime"] = pd.to_datetime(chat_data["CreateTime"]) - pd.Timedelta(hours=4)

View File

@ -10,16 +10,12 @@ import subprocess
import sys
import time
import uvicorn
import mimetypes
import logging
from logging.handlers import RotatingFileHandler
from uvicorn.config import LOGGING_CONFIG
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, FileResponse
from starlette.responses import RedirectResponse
from .utils import gc, is_port_in_use, server_loger
from .rjson import ReJson
@ -29,23 +25,20 @@ from .local_server import ls_api
from pywxdump import __version__
def gen_fastapi_app(handler, origins=None):
app = FastAPI(title="wxdump", description="微信工具", version=__version__,
def gen_fastapi_app():
app = FastAPI(title="pywxdump", description="微信工具", version=__version__,
terms_of_service="https://github.com/xaoyaoo/pywxdump",
contact={"name": "xaoyaoo", "url": "https://github.com/xaoyaoo/pywxdump"},
license_info={"name": "MIT License",
"url": "https://github.com/xaoyaoo/PyWxDump/blob/master/LICENSE"})
license_info={"name": "MIT License", "url": "https://github.com/xaoyaoo/PyWxDump/blob/master/LICENSE"})
web_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "ui", "web") # web文件夹路径
# 跨域
if not origins:
origins = [
"http://localhost:5000",
"http://127.0.0.1:5000",
"http://localhost:8080", # 开发环境的客户端地址"
# "http://0.0.0.0:5000",
# "*"
]
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, # 允许所有源
@ -54,18 +47,11 @@ def gen_fastapi_app(handler, origins=None):
allow_headers=["*"], # 允许所有头
)
@app.on_event("startup")
async def startup_event():
logger = logging.getLogger("uvicorn")
logger.addHandler(handler)
# 错误处理
@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 index():
@ -76,39 +62,15 @@ def gen_fastapi_app(handler, origins=None):
app.include_router(rs_api, prefix='/api/rs', tags=['远程api'])
app.include_router(ls_api, prefix='/api/ls', tags=['本地api'])
# 根据文件类型设置mime_type返回文件
@app.get("/s/{filename:path}")
async def serve_file(filename: str):
# 构建完整的文件路径
file_path = os.path.join(web_path, filename)
file_path = os.path.abspath(file_path)
# 检查文件是否存在
if os.path.isfile(file_path):
# 获取文件 MIME 类型
mime_type, _ = mimetypes.guess_type(file_path)
# 如果 MIME 类型为空,则默认为 application/octet-stream
if mime_type is None:
mime_type = "application/octet-stream"
server_loger.warning(f"[+] 无法获取文件 MIME 类型,使用默认值:{mime_type}")
if file_path.endswith(".js"):
mime_type = "text/javascript"
server_loger.info(f"[+] 文件 {file_path} MIME 类型:{mime_type}")
# 返回文件
return FileResponse(file_path, media_type=mime_type)
# 如果文件不存在,返回 404
return {"detail": "Not Found"}, 404
# 静态文件挂载
# if os.path.exists(os.path.join(web_path, "index.html")):
# app.mount("/s", StaticFiles(directory=web_path), name="static")
web_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), "ui", "web")
if os.path.exists(os.path.join(web_path, "index.html")):
app.mount("/s", StaticFiles(directory=web_path), name="static")
return app
def start_server(port=5000, online=False, debug=False, isopenBrowser=True,
merge_path="", wx_path="", my_wxid="", ):
def start_server(port=5000, online=False, debug=False, isopenBrowser=True):
"""
启动flask
:param port: 端口号
@ -117,28 +79,12 @@ def start_server(port=5000, online=False, debug=False, isopenBrowser=True,
:param isopenBrowser: 是否自动打开浏览器
:return:
"""
work_path = os.path.join(os.getcwd(), "wxdump_work") # 临时文件夹,用于存放图片等 # 全局变量
# 全局变量
work_path = os.path.join(os.getcwd(), "wxdump_work") # 临时文件夹,用于存放图片等
if not os.path.exists(work_path):
os.makedirs(work_path, exist_ok=True)
os.makedirs(work_path)
server_loger.info(f"[+] 创建临时文件夹:{work_path}")
print(f"[+] 创建临时文件夹:{work_path}")
# 日志处理,写入到文件
log_format = '[{levelname[0]}] {asctime} [{name}:{levelno}] {pathname}:{lineno} {message}'
log_datefmt = '%Y-%m-%d %H:%M:%S'
log_file_path = os.path.join(work_path, "wxdump.log")
file_handler = RotatingFileHandler(log_file_path, mode="a", maxBytes=10 * 1024 * 1024, backupCount=3)
formatter = logging.Formatter(fmt=log_format, datefmt=log_datefmt, style='{')
file_handler.setFormatter(formatter)
wx_core_logger = logging.getLogger("wx_core")
db_prepare = logging.getLogger("db_prepare")
# 这几个日志处理器为本项目的日志处理器
server_loger.addHandler(file_handler)
wx_core_logger.addHandler(file_handler)
db_prepare.addHandler(file_handler)
conf_file = os.path.join(work_path, "conf_auto.json") # 用于存放各种基础信息
auto_setting = "auto_setting"
env_file = os.path.join(work_path, ".env") # 用于存放环境变量
@ -152,15 +98,6 @@ def start_server(port=5000, online=False, debug=False, isopenBrowser=True,
f.write(f"PYWXDUMP_CONF_FILE = '{conf_file}'\n")
f.write(f"PYWXDUMP_AUTO_SETTING = '{auto_setting}'\n")
if merge_path and os.path.exists(merge_path):
my_wxid = my_wxid if my_wxid else "wxid_dbshow"
gc.set_conf(my_wxid, "wxid", my_wxid) # 初始化wxid
gc.set_conf(my_wxid, "merge_path", merge_path) # 初始化merge_path
gc.set_conf(my_wxid, "wx_path", wx_path) # 初始化wx_path
db_config = {"key": my_wxid, "type": "sqlite", "path": merge_path}
gc.set_conf(my_wxid, "db_config", db_config) # 初始化db_config
gc.set_conf(auto_setting, "last", my_wxid) # 初始化last
# 检查端口是否被占用
if online:
host = '0.0.0.0'
@ -191,25 +128,10 @@ def start_server(port=5000, online=False, debug=False, isopenBrowser=True,
time.sleep(1)
server_loger.info(f"启动flask服务host:port{host}:{port}")
print(f"[+] 请使用浏览器访问 http://127.0.0.1:{port}/ 查看聊天记录")
print("[+] 请使用浏览器访问 http://127.0.0.1:5000/ 查看聊天记录")
global app
print(f"[+] 如需查看api文档请访问 http://127.0.0.1:{port}/docs ")
origins = [
f"http://localhost:{port}",
f"http://{host}:{port}",
f"http://localhost:8080", # 开发环境的客户端地址"
# f"http://0.0.0.0:{port}",
# "*"
]
app = gen_fastapi_app(file_handler, origins)
LOGGING_CONFIG["formatters"]["default"]["fmt"] = "[%(asctime)s] %(levelprefix)s %(message)s"
LOGGING_CONFIG["formatters"]["access"][
"fmt"] = '[%(asctime)s] %(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s'
config = uvicorn.Config(app=app, host=host, port=port, reload=debug, log_level="info", workers=1, env_file=env_file)
server = uvicorn.Server(config)
server.run()
# uvicorn.run(app=app, host=host, port=port, reload=debug, log_level="info", workers=1, env_file=env_file)
app = gen_fastapi_app()
uvicorn.run(app=app, host=host, port=port, reload=debug, log_level="info", workers=1, env_file=env_file)
app = None

View File

@ -30,8 +30,7 @@ def export_csv(wxid, outpath, db_config, my_wxid="我", page_size=5000):
users = {}
for i in range(0, chatCount, page_size):
start_index = i
data, users_t = db.get_msgs(wxid, start_index, page_size)
print(users, users_t)
data, users_t = db.get_msg_list(wxid, start_index, page_size)
users.update(users_t)
if len(data) == 0:

View File

@ -46,7 +46,6 @@ def init_last(my_wxid: str = Body(..., embed=True)):
:return:
"""
my_wxid = my_wxid.strip().strip("'").strip('"') if isinstance(my_wxid, str) else ""
ls_loger.info(f"[+] init_last: {my_wxid}")
if not my_wxid:
my_wxid = gc.get_conf(gc.at, "last")
if not my_wxid: return ReJson(1001, body="my_wxid is required")
@ -208,10 +207,12 @@ def get_real_time_msg():
"""
my_wxid = gc.get_conf(gc.at, "last")
if not my_wxid: return ReJson(1001, body="my_wxid is required")
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:
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 = gc.get_conf(gc.at, "real_time_exe_path")
@ -259,7 +260,7 @@ def get_biasaddr(request: BiasAddrRequest):
mobile = request.mobile
name = request.name
account = request.account
key = request.key
key = request.json.key
wxdbPath = request.wxdbPath
if not mobile or not name or not account:
return ReJson(1002)

View File

@ -90,7 +90,7 @@ def user_labels_dict():
@rs_api.post('/user_list')
@error9999
def user_list(word: str = Body("", embed=True), wxids: List[str] = Body(None), labels: List[str] = Body(None)):
def user_list(word: str = "", wxids: List[str] = None, labels: List[str] = None):
"""
获取联系人列表可用于搜索
:return:
@ -102,7 +102,7 @@ def user_list(word: str = Body("", embed=True), wxids: List[str] = Body(None), l
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)
users = db.get_user(word=word, wxids=wxids, labels=labels)
users = db.get_user(word, wxids, labels)
return ReJson(0, users)

View File

@ -290,7 +290,7 @@ class MainShowChatRecords(BaseSubMainClass):
print("[-] 输入数据库路径不存在")
return
start_server(merge_path=merge_path, wx_path=args.wx_path, 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):

View File

@ -5,8 +5,6 @@
# Author: xaoyaoo
# Date: 2024/05/18
# -------------------------------------------------------------------------------
from collections import defaultdict
from .dbbase import DatabaseBase
from .utils import timestamp2str, xml2dict
@ -41,8 +39,7 @@ class FavoriteHandler(DatabaseBase):
"""
return: [(FavLocalID, TagName)]
"""
sql = ("select DISTINCT A.FavLocalID, B.TagName "
"from FavBindTagDatas A, FavTagDatas B where A.TagLocalID = B.LocalID")
sql = "select A.FavLocalID, B.TagName from FavBindTagDatas A, FavTagDatas B where A.TagLocalID = B.LocalID"
FavBindTags = self.execute(sql)
return FavBindTags
@ -128,18 +125,6 @@ class FavoriteHandler(DatabaseBase):
FavTagsDict = {}
for FavLocalID, TagName in FavTags:
FavTagsDict[FavLocalID] = FavTagsDict.get(FavLocalID, []) + [TagName]
rdata = []
for item in FavItemsList:
processed_item = {
key: item[i] for i, key in enumerate(FavItemsFields.keys())
}
processed_item['UpdateTime'] = timestamp2str(processed_item['UpdateTime'])
processed_item['XmlBuf'] = xml2dict(processed_item['XmlBuf'])
processed_item['TypeName'] = Favorite_type_converter(processed_item['Type'])
processed_item['FavData'] = FavDataDict.get(processed_item['FavLocalID'], [])
processed_item['Tags'] = FavTagsDict.get(processed_item['FavLocalID'], [])
rdata.append(processed_item)
try:
import pandas as pd
except ImportError:
@ -148,7 +133,7 @@ class FavoriteHandler(DatabaseBase):
pf.columns = FavItemsFields.keys() # set column names
pf["UpdateTime"] = pf["UpdateTime"].apply(timestamp2str) # 处理时间
pf["XmlBuf"] = pf["XmlBuf"].apply(xml2dict) # 处理xml
pf["TypeName"] = pf["Type"].apply(Favorite_type_converter) # 添加类型名称列
pf["TypeName"] = pf["Type"].apply(FavoriteTypeId2Name) # 添加类型名称列
pf["FavData"] = pf["FavLocalID"].apply(lambda x: FavDataDict.get(x, [])) # 添加数据列
pf["Tags"] = pf["FavLocalID"].apply(lambda x: FavTagsDict.get(x, [])) # 添加标签列
pf = pf.fillna("") # 去掉Nan
@ -156,15 +141,8 @@ class FavoriteHandler(DatabaseBase):
return rdata
def Favorite_type_converter(type_id_or_name: [str, int]):
"""
收藏类型ID与名称转换
名称(str)=>ID(int)
ID(int)=>名称(str)
:param type_id_or_name: 消息类型ID或名称
:return: 消息类型名称或ID
"""
type_name_dict = defaultdict(lambda: "未知", {
def FavoriteTypeId2Name(Type):
TypeNameDict = {
1: "文本", # 文本 已测试
2: "图片", # 图片 已测试
3: "语音", # 语音
@ -176,11 +154,5 @@ def Favorite_type_converter(type_id_or_name: [str, int]):
14: "聊天记录", # 聊天记录 已测试
16: "群聊视频", # 群聊中的视频 可能
18: "笔记" # 笔记 已测试
})
if isinstance(type_id_or_name, int):
return type_name_dict[type_id_or_name]
elif isinstance(type_id_or_name, str):
return next((k for k, v in type_name_dict.items() if v == type_id_or_name), (0, 0))
else:
raise ValueError("Invalid input type")
}
return TypeNameDict.get(Type, "未知")

View File

@ -95,8 +95,6 @@ class MicroHandler(DatabaseBase):
"WHERE S.strUsrName!='@publicUser' "
"ORDER BY S.nTime DESC;"
)
db_loger.info(f"get_session_list sql: {sql}")
ret = self.execute(sql)
if not ret:
return sessions
@ -141,8 +139,6 @@ class MicroHandler(DatabaseBase):
"ON A.Username = SubQuery.Username AND LastReadedCreateTime = SubQuery.MaxLastReadedCreateTime "
"ORDER BY A.LastReadedCreateTime DESC;"
)
db_loger.info(f"get_recent_chat_wxid sql: {sql}")
result = self.execute(sql)
if not result:
return []
@ -198,7 +194,6 @@ class MicroHandler(DatabaseBase):
sql_label = " OR ".join(sql_label)
sql = sql.replace(";", f"AND ({sql_label}) ;")
db_loger.info(f"get_user_list sql: {sql}")
result = self.execute(sql)
if not result:
return users
@ -218,11 +213,7 @@ class MicroHandler(DatabaseBase):
users[UserName] = {
"wxid": UserName, "nickname": NickName, "remark": Remark, "account": Alias,
"describe": describe, "headImgUrl": bigHeadImgUrl if bigHeadImgUrl else "",
"ExtraBuf": ExtraBuf, "LabelIDList": tuple(LabelIDList),
"extra": None}
extras = self.get_room_list(roomwxids=filter(lambda x: "@" in x, users.keys()))
for UserName in users:
users[UserName]["extra"] = extras.get(UserName, None)
"ExtraBuf": ExtraBuf, "LabelIDList": tuple(LabelIDList)}
return users
@db_error
@ -250,9 +241,8 @@ class MicroHandler(DatabaseBase):
sql = sql.replace(";",
f"AND A.ChatRoomName LIKE '%{word}%' ;")
if roomwxids:
sql = sql.replace(";", f"AND A.ChatRoomName IN ('" + "','".join(roomwxids) + "') ;")
sql = sql.replace(";", f"AND A.UserName IN ('" + "','".join(roomwxids) + "') ;")
db_loger.info(f"get_room_list sql: {sql}")
result = self.execute(sql)
if not result:
return rooms
@ -267,7 +257,7 @@ class MicroHandler(DatabaseBase):
DisplayNameList = DisplayNameList.split("^G")
RoomData = ChatRoom_RoomData(RoomData)
wxid2roomNickname = {}
wxid2remark = {}
if RoomData:
rd = []
for k, v in RoomData.items():
@ -276,20 +266,13 @@ class MicroHandler(DatabaseBase):
for i in rd:
try:
if isinstance(i, dict) and isinstance(i.get('1'), str) and i.get('2'):
wxid2roomNickname[i['1']] = i["2"]
wxid2remark[i['1']] = i["2"]
except Exception as e:
db_loger.error(f"wxid2remark: ChatRoomName:{ChatRoomName}, {i} error:{e}", exc_info=True)
wxid2userinfo = self.get_user_list(wxids=UserNameList)
for i in wxid2userinfo:
wxid2userinfo[i]["roomNickname"] = wxid2roomNickname.get(i, "")
owner = wxid2userinfo.get(Reserved2, Reserved2)
rooms[ChatRoomName] = {
"wxid": ChatRoomName, "roomWxids": UserNameList, "IsShowName": IsShowName,
"ChatRoomFlag": ChatRoomFlag, "SelfDisplayName": SelfDisplayName,
"owner": owner, "wxid2userinfo": wxid2userinfo,
"wxid": ChatRoomName, "UserNameList": UserNameList, "DisplayNameList": DisplayNameList,
"ChatRoomFlag": ChatRoomFlag, "IsShowName": IsShowName, "SelfDisplayName": SelfDisplayName,
"owner": Reserved2, "wxid2remark": wxid2remark,
"Announcement": Announcement, "AnnouncementEditor": AnnouncementEditor,
"AnnouncementPublishTime": AnnouncementPublishTime}
return rooms

View File

@ -56,7 +56,7 @@ class OpenIMContactHandler(DatabaseBase):
users[UserName] = {
"wxid": UserName, "nickname": NickName, "remark": Remark, "account": UserName,
"describe": '', "headImgUrl": BigHeadImgUrl if BigHeadImgUrl else "",
"ExtraBuf": None, "LabelIDList": tuple(), "extra": None}
"ExtraBuf": None, "LabelIDList": tuple()}
return users

View File

@ -114,7 +114,7 @@ class DatabaseBase(DatabaseSingletonBase):
if isinstance(required_tables, str):
required_tables = [required_tables]
rbool = all(table.lower() in self.existed_tables for table in (required_tables or []))
if not rbool: db_loger.warning(f"{required_tables=}\n{self.existed_tables=}\n{rbool=}")
if not rbool: db_loger.warning(f"{required_tables=}\n{self.existed_tables=}\n{rbool=}\n")
return rbool
def execute(self, sql, params=None):

View File

@ -8,4 +8,4 @@
from .wx_info import get_wx_info, get_wx_db, get_core_db
from .get_bias_addr import BiasAddr
from .decryption import batch_decrypt, decrypt
from .merge_db import merge_db, decrypt_merge, merge_real_time_db, all_merge_real_time_db
from .merge_db import merge_db, decrypt_merge, merge_real_time_db, all_merge_real_time_db

View File

@ -4,7 +4,6 @@
# Description:
# Author: xaoyaoo
# Date: 2023/08/21
# 注:该部分注释为最初学习使用,仅作参考
# 微信数据库采用的加密算法是256位的AES-CBC。数据库的默认的页大小是4096字节即4KB其中每一个页都是被单独加解密的。
# 加密文件的每一个页都有一个随机的初始化向量,它被保存在每一页的末尾。
# 加密文件的每一页都存有着消息认证码算法使用的是HMAC-SHA1安卓数据库使用的是SHA512。它也被保存在每一页的末尾。

View File

@ -10,31 +10,14 @@ import json
import os
import re
import sys
from ctypes import wintypes
import psutil
import pymem
from .utils import get_exe_version, get_exe_bit, verify_key
from .utils import get_process_list, get_memory_maps, get_process_exe_path, get_file_version_info
from .utils import search_memory
ReadProcessMemory = ctypes.windll.kernel32.ReadProcessMemory if sys.platform == "win32" else None
void_p = ctypes.c_void_p
# 定义常量
PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_VM_READ = 0x0010
kernel32 = ctypes.WinDLL('kernel32', use_last_error=True)
OpenProcess = kernel32.OpenProcess
OpenProcess.restype = wintypes.HANDLE
OpenProcess.argtypes = [wintypes.DWORD, wintypes.BOOL, wintypes.DWORD]
CloseHandle = kernel32.CloseHandle
CloseHandle.restype = wintypes.BOOL
CloseHandle.argtypes = [wintypes.HANDLE]
class BiasAddr:
def __init__(self, account, mobile, name, key, db_path):
@ -78,25 +61,10 @@ class BiasAddr:
return False, "[-] WeChat No Run"
def search_memory_value(self, value: bytes, module_name="WeChatWin.dll"):
start_adress = 0x7FFFFFFFFFFFFFFF
end_adress = 0
memory_maps = get_memory_maps(self.pid)
for module in memory_maps:
if module.FileName and module_name in module.FileName:
s = module.BaseAddress
e = module.BaseAddress + module.RegionSize
start_adress = s if s < start_adress else start_adress
end_adress = e if e > end_adress else end_adress
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, False, self.pid)
ret = search_memory(hProcess, value, max_num=3, start_address=start_adress,
end_address=end_adress)
ret = ret[-1] - start_adress if len(ret) > 0 else 0
# # 创建 Pymem 对象
# module = pymem.process.module_from_name(self.pm.process_handle, module_name)
# ret = self.pm.pattern_scan_module(value, module, return_multiple=True)
# ret = ret[-1] - module.lpBaseOfDll if len(ret) > 0 else 0
# 创建 Pymem 对象
module = pymem.process.module_from_name(self.pm.process_handle, module_name)
ret = self.pm.pattern_scan_module(value, module, return_multiple=True)
ret = ret[-1] - module.lpBaseOfDll if len(ret) > 0 else 0
return ret
def get_key_bias1(self):
@ -113,6 +81,7 @@ class BiasAddr:
module = pymem.process.module_from_name(self.process_handle, self.module_name)
keyBytes = b'-----BEGIN PUBLIC KEY-----\n...'
publicKeyList = pymem.pattern.pattern_scan_all(self.process_handle, keyBytes, return_multiple=True)
keyaddrs = []
for addr in publicKeyList:
keyBytes = addr.to_bytes(byteLen, byteorder="little", signed=True) # 低位在前

View File

@ -161,23 +161,8 @@ def merge_db(db_paths: List[dict], save_path: str = "merge.db", is_merge_data: b
# 创建包含 NULL 值比较的 UNIQUE 索引
index_name = f"{table}_unique_index"
coalesce_columns = ','.join(f"COALESCE({column}, '')" for column in columns)
sql = f"CREATE UNIQUE INDEX IF NOT EXISTS {index_name} ON {table} ({coalesce_columns})" # 创建索引
# ****** 该部分代码来源于 https://github.com/xaoyaoo/PyWxDump/issues/176
# 防止数据重复导致索引创建失败
sql_if_exists_index = f"SELECT 1 FROM sqlite_master WHERE type='index' AND name='{index_name}' AND tbl_name='{table}';"
out_cursor.execute(sql_if_exists_index)
ret_if_exists_index = out_cursor.fetchone()
if ret_if_exists_index is None:
# 之前没创建过索引 先执行删除删除相同数据
# DELETE FROM employees WHERE ROWID NOT IN ( SELECT MIN(ROWID) FROM employees GROUP BY name, position);
str_columns = ','.join(columns)
# sql_clear_same = f"DELETE FROM {table} WHERE ROWID NOT IN (SELECT MIN(ROWID) FROM {table} GROUP BY {str_columns});"
sql_clear_same = f'''WITH Ranked AS (SELECT ROWID, ROW_NUMBER() OVER (PARTITION BY {str_columns} ORDER BY ROWID) AS rn FROM {table})
DELETE FROM {table} WHERE ROWID IN (SELECT ROWID FROM Ranked WHERE rn > 1);'''
out_cursor.execute(sql_clear_same)
out_cursor.execute(sql) # 执行创建索引
sql = f"CREATE UNIQUE INDEX IF NOT EXISTS {index_name} ON {table} ({coalesce_columns})"
out_cursor.execute(sql)
# 插入sync_log
sql_query_sync_log = f"SELECT src_count FROM sync_log WHERE db_path=? AND tbl_name=?"
@ -471,7 +456,7 @@ def all_merge_real_time_db(key, wx_path, merge_path: str, real_time_exe_path: st
合并所有实时数据库
这是全量合并会有可能产生重复数据需要自行去重
:param key: 解密密钥
:param wx_path: 微信文件夹路径 egC:\*****\WeChat Files\wxid*******
:param wx_path: 微信路径
:param merge_path: 合并后的数据库路径 eg: C:\\*******\\WeChat Files\\wxid_*********\\merge.db
:param real_time_exe_path: 实时数据库合并工具路径
:return:

Binary file not shown.

View File

@ -12,7 +12,7 @@ lxml
dbutils
psutil
pymem
pydantic==2.7.0
fastapi
uvicorn
python-dotenv

View File

@ -151,9 +151,9 @@ if package_path:
require_path = os.path.join(os.path.dirname(current_path), "requirements.txt") # requirements.txt 路径
with open(require_path, "r", encoding="utf-8") as f:
hidden_imports = f.read().splitlines()
hidden_imports = [i.replace('-', '_').split("=")[0].split("~")[0] for i in hidden_imports if
hidden_imports = [i.replace('-', '_') for i in hidden_imports if
i and i not in ["setuptools", "wheel"]] # 去掉setuptools、wheel
hidden_imports += ["pywxdump", "pywxdump.db", "pywxdump.db.__init__.utils"]
hidden_imports += ["pywxdump", "pywxdump.db","pywxdump.db.__init__.utils"]
# 获取 ui 文件夹下的所有文件 用于打包
root_path = os.path.join(package_path, 'pywxdump')

View File

@ -11,7 +11,6 @@ import time
def custom_sort_key(tag):
tag = tag.split(',')[0]
if tag == 'python':
return "000.000.000"
elif tag == 'HEAD':
@ -69,7 +68,6 @@ log = log.replace("(HEAD -> master)", "")
log = log.replace("(HEAD -> master, origin/master, origin/HEAD)", "")
log = log.replace("(origin/master, origin/HEAD)", "")
log = log.replace("HEAD -> master, ", "").replace(", origin/master, origin/HEAD", "")
log = log.replace("(backup/master)", "")
# 按照tag分割
log = log.split("(tag: ")

View File

@ -6,6 +6,7 @@
# Date: 2024/07/02
# -------------------------------------------------------------------------------
import os
import sys
import time
# 获取当前文件所在目录

View File

@ -5,7 +5,10 @@
# Author: xaoyaoo
# Date: 2023/10/15
# -------------------------------------------------------------------------------
import pywxdump
from pywxdump import WX_OFFS_PATH, WX_OFFS
from pywxdump import BiasAddr
from pywxdump.wx_info import read_info
mobile = '13800138000'
name = '张三'
@ -15,3 +18,4 @@ db_path = None # "xxxxxx"
vlp = None # WX_OFFS_PATH
# 调用 run 函数,并传入参数
rdata = BiasAddr(account, mobile, name, key, db_path).run(True, vlp)

View File

@ -5,9 +5,33 @@
# Author: xaoyaoo
# Date: 2023/11/15
# -------------------------------------------------------------------------------
try:
from flask import Flask, request, jsonify, render_template, g
import logging
from pywxdump.show_chat.main_window import app_show_chat, get_user_list
except Exception as e:
print(e)
print("[-] 请安装flask( pip install flask )")
assert "[-] 请安装flask( pip install flask )"
from pywxdump import start_server
app = Flask(__name__, template_folder='./show_chat/templates')
app.logger.setLevel(logging.ERROR)
merge_path = r"D:\****.db"
msg_path = r"xxxxxx"
micro_path = r"xxxxxx"
media_path = r"xxxxxx"
filestorage_path = r"xxxxxx"
start_server(merge_path=merge_path)
@app.before_request
def before_request():
g.MSG_ALL_db_path = msg_path
g.MicroMsg_db_path = micro_path
g.MediaMSG_all_db_path = media_path
g.FileStorage_path = filestorage_path
g.USER_LIST = get_user_list(msg_path, micro_path)
app.register_blueprint(app_show_chat)
print("[+] 请使用浏览器访问 http://127.0.0.1:5000/ 查看聊天记录")
app.run(debug=False)

View File

@ -5,6 +5,8 @@
# Author: xaoyaoo
# Date: 2023/11/15
# -------------------------------------------------------------------------------
from pywxdump import WX_OFFS_PATH, WX_OFFS
from pywxdump import batch_decrypt
key = "xxxxxx" # 解密密钥

View File

@ -5,9 +5,10 @@
# Author: xaoyaoo
# Date: 2023/10/21
# -------------------------------------------------------------------------------
from pywxdump import get_wx_info, WX_OFFS
from pywxdump.wx_info import read_info
from pywxdump import WX_OFFS_PATH, WX_OFFS
def test_read_info():
result = get_wx_info(WX_OFFS, is_logging=True) # 读取微信信息
assert result is not None
result = read_info(WX_OFFS, is_logging=True) # 读取微信信息
assert result is not None