Merge remote-tracking branch 'refs/remotes/cllcode/master' into feature/20240629
This commit is contained in:
commit
bc90597e54
@ -14,8 +14,6 @@
|
|||||||
[](https://pypistats.org/packages/pywxdump)
|
[](https://pypistats.org/packages/pywxdump)
|
||||||
[](https://github.com/xaoyaoo/PyWxDump/blob/master/LICENSE)
|
[](https://github.com/xaoyaoo/PyWxDump/blob/master/LICENSE)
|
||||||
|
|
||||||
V3.0.* 有很多bug,如果你有问题,请使用V2.4.71版本,或者等待后续版本(发现bug请提交issue)
|
|
||||||
|
|
||||||
* Welcome to provide more ideas or code to improve this project together.
|
* Welcome to provide more ideas or code to improve this project together.
|
||||||
|
|
||||||
### If you are a novice, please pay attention to the Official Accounts: `逍遥之芯` (the QR code is below), and reply: `PyWxDump` to get a picture text tutorial.
|
### If you are a novice, please pay attention to the Official Accounts: `逍遥之芯` (the QR code is below), and reply: `PyWxDump` to get a picture text tutorial.
|
||||||
|
@ -1,5 +1,20 @@
|
|||||||
## v3.0.36.(待发布)
|
## v3.0.38.(待发布)
|
||||||
|
|
||||||
|
-
|
||||||
|
|
||||||
|
## v3.0.37
|
||||||
|
|
||||||
|
- 增加公众号消息显示
|
||||||
|
- fix and add
|
||||||
|
- add (分享)卡片式链接 解析
|
||||||
|
- UPDATE CHANGELOG.md
|
||||||
|
- release_new_version
|
||||||
|
|
||||||
|
## v3.0.36
|
||||||
|
|
||||||
|
- fix
|
||||||
|
- Update README.md
|
||||||
|
- add wx 3.9.11.19
|
||||||
- UPDATE CHANGELOG.md
|
- UPDATE CHANGELOG.md
|
||||||
|
|
||||||
## v3.0.35
|
## v3.0.35
|
||||||
|
@ -13,7 +13,7 @@ from .wx_info import merge_copy_db, merge_msg_db, merge_media_msg_db, merge_db,
|
|||||||
all_merge_real_time_db
|
all_merge_real_time_db
|
||||||
from .analyzer import DBPool
|
from .analyzer import DBPool
|
||||||
from .dbpreprocess import get_user_list, get_recent_user_list, wxid2userinfo, ParsingMSG, ParsingMicroMsg, \
|
from .dbpreprocess import get_user_list, get_recent_user_list, wxid2userinfo, ParsingMSG, ParsingMicroMsg, \
|
||||||
ParsingMediaMSG, ParsingOpenIMContact, ParsingFavorite
|
ParsingMediaMSG, ParsingOpenIMContact, ParsingFavorite,ParsingPublicMsg
|
||||||
from .server import start_falsk
|
from .server import start_falsk
|
||||||
import os, json
|
import os, json
|
||||||
|
|
||||||
@ -28,4 +28,4 @@ except:
|
|||||||
# PYWXDUMP_ROOT_PATH = os.path.dirname(__file__)
|
# PYWXDUMP_ROOT_PATH = os.path.dirname(__file__)
|
||||||
# db_init = DBPool("DBPOOL_INIT")
|
# db_init = DBPool("DBPOOL_INIT")
|
||||||
|
|
||||||
__version__ = "3.0.35"
|
__version__ = "3.0.37"
|
||||||
|
@ -28,7 +28,7 @@ from pywxdump.api.utils import read_session, get_session_wxids, save_session, er
|
|||||||
from pywxdump import read_info, VERSION_LIST, batch_decrypt, BiasAddr, merge_db, decrypt_merge, merge_real_time_db
|
from pywxdump import read_info, VERSION_LIST, batch_decrypt, BiasAddr, merge_db, decrypt_merge, merge_real_time_db
|
||||||
|
|
||||||
from pywxdump.dbpreprocess import wxid2userinfo, ParsingMSG, get_user_list, get_recent_user_list, ParsingMediaMSG, \
|
from pywxdump.dbpreprocess import wxid2userinfo, ParsingMSG, get_user_list, get_recent_user_list, ParsingMediaMSG, \
|
||||||
download_file, export_csv, export_json, ParsingMicroMsg
|
download_file, export_csv, export_json, ParsingMicroMsg, ParsingPublicMsg
|
||||||
from pywxdump.dbpreprocess.utils import dat2img
|
from pywxdump.dbpreprocess.utils import dat2img
|
||||||
|
|
||||||
# app = Flask(__name__, static_folder='../ui/web/dist', static_url_path='/')
|
# app = Flask(__name__, static_folder='../ui/web/dist', static_url_path='/')
|
||||||
@ -316,6 +316,8 @@ def msg_count():
|
|||||||
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
if not my_wxid: return ReJson(1001, body="my_wxid is required")
|
||||||
merge_path = read_session(g.sf, my_wxid, "merge_path")
|
merge_path = read_session(g.sf, my_wxid, "merge_path")
|
||||||
chat_count = ParsingMSG(merge_path).msg_count(wxid)
|
chat_count = ParsingMSG(merge_path).msg_count(wxid)
|
||||||
|
if None in chat_count:
|
||||||
|
chat_count = ParsingPublicMsg(merge_path).msg_count(wxid)
|
||||||
return ReJson(0, chat_count)
|
return ReJson(0, chat_count)
|
||||||
|
|
||||||
|
|
||||||
@ -412,6 +414,9 @@ def get_msgs():
|
|||||||
|
|
||||||
parsing_msg = ParsingMSG(merge_path)
|
parsing_msg = ParsingMSG(merge_path)
|
||||||
msgs, wxid_list = parsing_msg.msg_list(wxid, start, limit)
|
msgs, wxid_list = parsing_msg.msg_list(wxid, start, limit)
|
||||||
|
if not msgs:
|
||||||
|
parsing_public_msg = ParsingPublicMsg(merge_path)
|
||||||
|
msgs, wxid_list = parsing_public_msg.msg_list(wxid, start, limit)
|
||||||
wxid_list.append(my_wxid)
|
wxid_list.append(my_wxid)
|
||||||
user_list = wxid2userinfo(merge_path, merge_path, wxid_list)
|
user_list = wxid2userinfo(merge_path, merge_path, wxid_list)
|
||||||
return ReJson(0, {"msg_list": msgs, "user_list": user_list})
|
return ReJson(0, {"msg_list": msgs, "user_list": user_list})
|
||||||
|
@ -12,6 +12,7 @@ from .parsingMSG import ParsingMSG
|
|||||||
from .parsingMicroMsg import ParsingMicroMsg
|
from .parsingMicroMsg import ParsingMicroMsg
|
||||||
from .parsingMediaMSG import ParsingMediaMSG
|
from .parsingMediaMSG import ParsingMediaMSG
|
||||||
from .parsingOpenIMContact import ParsingOpenIMContact
|
from .parsingOpenIMContact import ParsingOpenIMContact
|
||||||
|
from .parsingPublicMsg import ParsingPublicMsg
|
||||||
from .utils import download_file
|
from .utils import download_file
|
||||||
|
|
||||||
from .export.exportCSV import export_csv
|
from .export.exportCSV import export_csv
|
||||||
|
@ -341,7 +341,8 @@ class ParsingMSG(DatabaseBase):
|
|||||||
"""
|
"""
|
||||||
获取单条消息详情,格式化输出
|
获取单条消息详情,格式化输出
|
||||||
"""
|
"""
|
||||||
localId, IsSender, StrContent, StrTalker, Sequence, Type, SubType, CreateTime, MsgSvrID, DisplayContent, CompressContent, BytesExtra, id = row
|
(localId, IsSender, StrContent, StrTalker, Sequence, Type, SubType, CreateTime, MsgSvrID,
|
||||||
|
DisplayContent, CompressContent, BytesExtra, id) = row
|
||||||
CreateTime = timestamp2str(CreateTime)
|
CreateTime = timestamp2str(CreateTime)
|
||||||
|
|
||||||
type_id = (Type, SubType)
|
type_id = (Type, SubType)
|
||||||
@ -416,6 +417,16 @@ class ParsingMSG(DatabaseBase):
|
|||||||
file_name = os.path.basename(url)
|
file_name = os.path.basename(url)
|
||||||
content["msg"] = file_name
|
content["msg"] = file_name
|
||||||
|
|
||||||
|
elif type_id == (49, 5): # (分享)卡片式链接
|
||||||
|
CompressContent = self.decompress_CompressContent(CompressContent)
|
||||||
|
CompressContent_tmp = xml2dict(CompressContent)
|
||||||
|
appmsg = CompressContent_tmp.get("appmsg", {})
|
||||||
|
title = appmsg.get("title", "")
|
||||||
|
des = appmsg.get("des", "")
|
||||||
|
url = appmsg.get("url", "")
|
||||||
|
content["msg"] = f"{title}\n{des}\n\n{url}"
|
||||||
|
content["src"] = url
|
||||||
|
|
||||||
elif type_id == (49, 19): # 合并转发的聊天记录
|
elif type_id == (49, 19): # 合并转发的聊天记录
|
||||||
CompressContent = self.decompress_CompressContent(CompressContent)
|
CompressContent = self.decompress_CompressContent(CompressContent)
|
||||||
content_tmp = xml2dict(CompressContent)
|
content_tmp = xml2dict(CompressContent)
|
||||||
|
94
pywxdump/dbpreprocess/parsingPublicMsg.py
Normal file
94
pywxdump/dbpreprocess/parsingPublicMsg.py
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
# -*- coding: utf-8 -*-#
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
# Name: parsingPublicMsg.py
|
||||||
|
# Description:
|
||||||
|
# Author: xaoyaoo
|
||||||
|
# Date: 2024/07/03
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# -*- coding: utf-8 -*-#
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
# Name: parsingMSG.py
|
||||||
|
# Description:
|
||||||
|
# Author: xaoyaoo
|
||||||
|
# Date: 2024/04/15
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from typing import Union, Tuple
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from .dbbase import DatabaseBase
|
||||||
|
from .parsingMSG import ParsingMSG
|
||||||
|
from .utils import get_md5, name2typeid, typeid2name, type_converter, timestamp2str, xml2dict, match_BytesExtra
|
||||||
|
import lz4.block
|
||||||
|
import blackboxprotobuf
|
||||||
|
|
||||||
|
|
||||||
|
class ParsingPublicMsg(ParsingMSG):
|
||||||
|
_class_name = "PublicMSG"
|
||||||
|
|
||||||
|
def msg_count(self, wxid: str = ""):
|
||||||
|
"""
|
||||||
|
获取聊天记录数量,根据wxid获取单个联系人的聊天记录数量,不传wxid则获取所有联系人的聊天记录数量
|
||||||
|
:param MSG_db_path: MSG.db 文件路径
|
||||||
|
:return: 聊天记录数量列表 {wxid: chat_count}
|
||||||
|
"""
|
||||||
|
if wxid:
|
||||||
|
sql = f"SELECT StrTalker, COUNT(*) FROM PublicMsg WHERE StrTalker='{wxid}';"
|
||||||
|
else:
|
||||||
|
sql = f"SELECT StrTalker, COUNT(*) FROM PublicMsg GROUP BY StrTalker ORDER BY COUNT(*) DESC;"
|
||||||
|
|
||||||
|
result = self.execute_sql(sql)
|
||||||
|
if not result:
|
||||||
|
return {}
|
||||||
|
df = pd.DataFrame(result, columns=["wxid", "msg_count"])
|
||||||
|
# # 排序
|
||||||
|
df = df.sort_values(by="msg_count", ascending=False)
|
||||||
|
# chat_counts : {wxid: chat_count}
|
||||||
|
chat_counts = df.set_index("wxid").to_dict()["msg_count"]
|
||||||
|
return chat_counts
|
||||||
|
|
||||||
|
def msg_count_total(self):
|
||||||
|
"""
|
||||||
|
获取聊天记录总数
|
||||||
|
:return: 聊天记录总数
|
||||||
|
"""
|
||||||
|
sql = "SELECT COUNT(*) FROM PublicMsg;"
|
||||||
|
result = self.execute_sql(sql)
|
||||||
|
if result and len(result) > 0:
|
||||||
|
chat_counts = result[0][0]
|
||||||
|
return chat_counts
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def msg_list(self, wxid="", start_index=0, page_size=500, msg_type: str = ""):
|
||||||
|
sql = (
|
||||||
|
"SELECT localId, IsSender, StrContent, StrTalker, Sequence, Type, SubType, CreateTime, MsgSvrID, "
|
||||||
|
"DisplayContent, CompressContent, BytesExtra, ROW_NUMBER() OVER (ORDER BY CreateTime ASC) AS id "
|
||||||
|
"FROM PublicMsg WHERE 1==1 "
|
||||||
|
"ORDER BY CreateTime ASC LIMIT ?, ?"
|
||||||
|
)
|
||||||
|
params = [start_index, page_size]
|
||||||
|
if msg_type:
|
||||||
|
sql = sql.replace("ORDER BY CreateTime ASC LIMIT ?, ?",
|
||||||
|
f"AND Type=? ORDER BY CreateTime ASC LIMIT ?,?")
|
||||||
|
params = [msg_type] + params
|
||||||
|
|
||||||
|
if wxid:
|
||||||
|
sql = sql.replace("WHERE 1==1", f"WHERE StrTalker=? ")
|
||||||
|
params = [wxid] + params
|
||||||
|
params = tuple(params)
|
||||||
|
result1 = self.execute_sql(sql, params)
|
||||||
|
if not result1:
|
||||||
|
return [], []
|
||||||
|
data = []
|
||||||
|
wxid_list = []
|
||||||
|
for row in result1:
|
||||||
|
tmpdata = self.msg_detail(row)
|
||||||
|
wxid_list.append(tmpdata["talker"])
|
||||||
|
data.append(tmpdata)
|
||||||
|
wxid_list = list(set(wxid_list))
|
||||||
|
return data, wxid_list
|
@ -390,5 +390,12 @@
|
|||||||
93550168,
|
93550168,
|
||||||
0,
|
0,
|
||||||
93551632
|
93551632
|
||||||
|
],
|
||||||
|
"3.9.11.19": [
|
||||||
|
93550296,
|
||||||
|
93551632,
|
||||||
|
93550104,
|
||||||
|
0,
|
||||||
|
93551568
|
||||||
]
|
]
|
||||||
}
|
}
|
@ -408,7 +408,7 @@ def get_core_db(wx_path: str, db_type: list = None) -> [str]:
|
|||||||
"""
|
"""
|
||||||
if not os.path.exists(wx_path):
|
if not os.path.exists(wx_path):
|
||||||
return False, f"[-] 目录不存在: {wx_path}"
|
return False, f"[-] 目录不存在: {wx_path}"
|
||||||
db_type_all = ["MSG", "MediaMSG", "MicroMsg", "OpenIMContact", "OpenIMMedia", "OpenIMMsg", "Favorite"]
|
db_type_all = ["MSG", "MediaMSG", "MicroMsg", "OpenIMContact", "OpenIMMedia", "OpenIMMsg", "Favorite", "PublicMsg"]
|
||||||
|
|
||||||
if not db_type:
|
if not db_type:
|
||||||
db_type = db_type_all
|
db_type = db_type_all
|
||||||
|
@ -14,6 +14,7 @@ import subprocess
|
|||||||
import time
|
import time
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
def merge_copy_db(db_path, save_path):
|
def merge_copy_db(db_path, save_path):
|
||||||
if isinstance(db_path, list) and len(db_path) == 1:
|
if isinstance(db_path, list) and len(db_path) == 1:
|
||||||
db_path = db_path[0]
|
db_path = db_path[0]
|
||||||
@ -299,7 +300,8 @@ def merge_db(db_paths, save_path="merge.db", CreateTime: int = 0, endCreateTime:
|
|||||||
return save_path
|
return save_path
|
||||||
|
|
||||||
|
|
||||||
def decrypt_merge(wx_path, key, outpath="", CreateTime: int = 0, endCreateTime: int = 0, db_type: List[str] = []) -> (bool, str):
|
def decrypt_merge(wx_path, key, outpath="", CreateTime: int = 0, endCreateTime: int = 0, db_type: List[str] = []) -> (
|
||||||
|
bool, str):
|
||||||
"""
|
"""
|
||||||
解密合并数据库 msg.db, microMsg.db, media.db,注意:会删除原数据库
|
解密合并数据库 msg.db, microMsg.db, media.db,注意:会删除原数据库
|
||||||
:param wx_path: 微信路径 eg: C:\*******\WeChat Files\wxid_*********
|
:param wx_path: 微信路径 eg: C:\*******\WeChat Files\wxid_*********
|
||||||
@ -319,7 +321,8 @@ def decrypt_merge(wx_path, key, outpath="", CreateTime: int = 0, endCreateTime:
|
|||||||
# 分割wx_path的文件名和父目录
|
# 分割wx_path的文件名和父目录
|
||||||
msg_dir = os.path.dirname(wx_path)
|
msg_dir = os.path.dirname(wx_path)
|
||||||
my_wxid = os.path.basename(wx_path)
|
my_wxid = os.path.basename(wx_path)
|
||||||
db_type_set: set[str] = {"MSG", "MediaMSG", "MicroMsg", "OpenIMContact", "OpenIMMedia", "OpenIMMsg", "Favorite"}
|
db_type_set: set[str] = {"MSG", "MediaMSG", "MicroMsg", "OpenIMContact", "OpenIMMedia", "OpenIMMsg", "Favorite",
|
||||||
|
"PublicMsg"}
|
||||||
if len(db_type) == 0:
|
if len(db_type) == 0:
|
||||||
db_type = list(db_type_set)
|
db_type = list(db_type_set)
|
||||||
else:
|
else:
|
||||||
|
32
tests/release_new_version.py
Normal file
32
tests/release_new_version.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# -*- coding: utf-8 -*-#
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
# Name: release_new_version.py
|
||||||
|
# Description:
|
||||||
|
# Author: xaoyaoo
|
||||||
|
# Date: 2024/07/02
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
# 获取当前文件所在目录
|
||||||
|
current_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
PyWxDump_path = os.path.dirname((current_path))
|
||||||
|
os.chdir(PyWxDump_path)
|
||||||
|
version_path = os.path.join(PyWxDump_path, "pywxdump", "__init__.py")
|
||||||
|
|
||||||
|
# 读取版本号 pywxdump/__init__.py 中的 __version__
|
||||||
|
with open(version_path, "r", encoding="utf-8") as f:
|
||||||
|
for line in f.readlines():
|
||||||
|
if line.startswith("__version__"):
|
||||||
|
version = line.split("=")[-1].strip().strip("\"'")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise RuntimeError("version not found")
|
||||||
|
|
||||||
|
# print("PyWxDump_path", PyWxDump_path)
|
||||||
|
# print("version", version)
|
||||||
|
print(f"git tag -a v{version} -m 'v{version} release'")
|
||||||
|
os.system(f"git tag -a v{version} -m 'v{version}'")
|
||||||
|
time.sleep(1)
|
||||||
|
os.system(f"git push origin v{version}")
|
Loading…
Reference in New Issue
Block a user