diff --git a/pywxdump/db/dbMSG.py b/pywxdump/db/dbMSG.py index 6b79aab..2c31933 100644 --- a/pywxdump/db/dbMSG.py +++ b/pywxdump/db/dbMSG.py @@ -65,6 +65,140 @@ class MsgHandler(DatabaseBase): msg_count.update({row[0]: row[1] for row in result}) return msg_count + @db_error + def get_msg_list(self, wxid="", start_index=0, page_size=500, msg_type: str = "", msg_sub_type: str = "", + start_createtime=None, end_createtime=None): + """ + 获取聊天记录列表 + :param wxid: wxid + :param start_index: 起始索引 + :param page_size: 页大小 + :param msg_type: 消息类型 + :param msg_sub_type: 消息子类型 + :param start_createtime: 开始时间 + :param end_createtime: 结束时间 + :return: 聊天记录列表 {"id": _id, "MsgSvrID": str(MsgSvrID), "type_name": type_name, "is_sender": IsSender, + "talker": talker, "room_name": StrTalker, "msg": msg, "src": src, "extra": {}, + "CreateTime": CreateTime, } + """ + if not self.tables_exist("MSG"): + return [], [] + + param = () + sql_wxid, param = ("AND StrTalker=? ", param + (wxid,)) if wxid else ("", param) + sql_type, param = ("AND Type=? ", param + (msg_type,)) if msg_type else ("", param) + sql_sub_type, param = ("AND SubType=? ", param + (msg_sub_type,)) if msg_type and msg_sub_type else ("", param) + sql_start_createtime, param = ("AND CreateTime>=? ", param + (start_createtime,)) if start_createtime else ( + "", param) + sql_end_createtime, param = ("AND CreateTime<=? ", param + (end_createtime,)) if end_createtime else ("", param) + + sql = ( + "SELECT localId,TalkerId,MsgSvrID,Type,SubType,CreateTime,IsSender,Sequence,StatusEx,FlagEx,Status," + "MsgSequence,StrContent,MsgServerSeq,StrTalker,DisplayContent,Reserved0,Reserved1,Reserved3," + "Reserved4,Reserved5,Reserved6,CompressContent,BytesExtra,BytesTrans,Reserved2," + "ROW_NUMBER() OVER (ORDER BY CreateTime ASC) AS id " + "FROM MSG WHERE 1=1 " + f"{sql_wxid}" + f"{sql_type}" + f"{sql_sub_type}" + f"{sql_start_createtime}" + f"{sql_end_createtime}" + f"ORDER BY CreateTime ASC LIMIT ?,?" + ) + param = param + (start_index, page_size) + result = self.execute(sql, param) + if not result: + return [], [] + + result_data = (self.get_msg_detail(row) for row in result) + rdata = list(result_data) # 转为列表 + wxid_list = {d['talker'] for d in rdata} # 创建一个无重复的 wxid 列表 + + return rdata, list(wxid_list) + + @db_error + def get_date_count(self, wxid='', start_time: int = 0, end_time: int = 0, time_format='%Y-%m-%d'): + """ + 获取每日聊天记录数量,包括发送者数量、接收者数量和总数。 + """ + if not self.tables_exist("MSG"): + return {} + if isinstance(start_time, str) and start_time.isdigit(): + start_time = int(start_time) + if isinstance(end_time, str) and end_time.isdigit(): + end_time = int(end_time) + + # if start_time or end_time is not an integer and not a float, set both to 0 + if not (isinstance(start_time, (int, float)) and isinstance(end_time, (int, float))): + start_time = 0 + end_time = 0 + params = () + + sql_wxid = "AND StrTalker = ? " if wxid else "" + params = params + (wxid,) if wxid else params + + sql_time = "AND CreateTime BETWEEN ? AND ? " if start_time and end_time else "" + params = params + (start_time, end_time) if start_time and end_time else params + + sql = (f"SELECT strftime('{time_format}', CreateTime, 'unixepoch', 'localtime') AS date, " + " COUNT(*) AS total_count ," + " SUM(CASE WHEN IsSender = 1 THEN 1 ELSE 0 END) AS sender_count, " + " SUM(CASE WHEN IsSender = 0 THEN 1 ELSE 0 END) AS receiver_count " + "FROM MSG " + "WHERE StrTalker NOT LIKE '%chatroom%' " + f"{sql_wxid} {sql_time} " + f"GROUP BY date ORDER BY date ASC;") + result = self.execute(sql, params) + + if not result: + return {} + # 将查询结果转换为字典 + result_dict = {} + for row in result: + date, total_count, sender_count, receiver_count = row + result_dict[date] = { + "sender_count": sender_count, + "receiver_count": receiver_count, + "total_count": total_count + } + return result_dict + + @db_error + def get_top_talker_count(self, top: int = 10, start_time: int = 0, end_time: int = 0): + """ + 获取聊天记录数量最多的联系人,他们聊天记录数量 + """ + if not self.tables_exist("MSG"): + return {} + if isinstance(start_time, str) and start_time.isdigit(): + start_time = int(start_time) + if isinstance(end_time, str) and end_time.isdigit(): + end_time = int(end_time) + + # if start_time or end_time is not an integer and not a float, set both to 0 + if not (isinstance(start_time, (int, float)) and isinstance(end_time, (int, float))): + start_time = 0 + end_time = 0 + + sql_time = f"AND CreateTime BETWEEN {start_time} AND {end_time} " if start_time and end_time else "" + sql = ( + "SELECT StrTalker, COUNT(*) AS count," + "SUM(CASE WHEN IsSender = 1 THEN 1 ELSE 0 END) AS sender_count, " + "SUM(CASE WHEN IsSender = 0 THEN 1 ELSE 0 END) AS receiver_count " + "FROM MSG " + "WHERE StrTalker NOT LIKE '%chatroom%' " + f"{sql_time} " + "GROUP BY StrTalker ORDER BY count DESC " + f"LIMIT {top};" + ) + result = self.execute(sql) + if not result: + return {} + # 将查询结果转换为字典 + result_dict = {row[0]: {"total_count": row[1], "sender_count": row[2], "receiver_count": row[3]} for row in + result} + return result_dict + # 单条消息处理 @db_error def get_msg_detail(self, row): @@ -250,140 +384,6 @@ class MsgHandler(DatabaseBase): "CreateTime": CreateTime, } return row_data - @db_error - def get_msg_list(self, wxid="", start_index=0, page_size=500, msg_type: str = "", msg_sub_type: str = "", - start_createtime=None, end_createtime=None): - """ - 获取聊天记录列表 - :param wxid: wxid - :param start_index: 起始索引 - :param page_size: 页大小 - :param msg_type: 消息类型 - :param msg_sub_type: 消息子类型 - :param start_createtime: 开始时间 - :param end_createtime: 结束时间 - :return: 聊天记录列表 {"id": _id, "MsgSvrID": str(MsgSvrID), "type_name": type_name, "is_sender": IsSender, - "talker": talker, "room_name": StrTalker, "msg": msg, "src": src, "extra": {}, - "CreateTime": CreateTime, } - """ - if not self.tables_exist("MSG"): - return [], [] - - param = () - sql_wxid, param = ("AND StrTalker=? ", param + (wxid,)) if wxid else ("", param) - sql_type, param = ("AND Type=? ", param + (msg_type,)) if msg_type else ("", param) - sql_sub_type, param = ("AND SubType=? ", param + (msg_sub_type,)) if msg_type and msg_sub_type else ("", param) - sql_start_createtime, param = ("AND CreateTime>=? ", param + (start_createtime,)) if start_createtime else ( - "", param) - sql_end_createtime, param = ("AND CreateTime<=? ", param + (end_createtime,)) if end_createtime else ("", param) - - sql = ( - "SELECT localId,TalkerId,MsgSvrID,Type,SubType,CreateTime,IsSender,Sequence,StatusEx,FlagEx,Status," - "MsgSequence,StrContent,MsgServerSeq,StrTalker,DisplayContent,Reserved0,Reserved1,Reserved3," - "Reserved4,Reserved5,Reserved6,CompressContent,BytesExtra,BytesTrans,Reserved2," - "ROW_NUMBER() OVER (ORDER BY CreateTime ASC) AS id " - "FROM MSG WHERE 1=1 " - f"{sql_wxid}" - f"{sql_type}" - f"{sql_sub_type}" - f"{sql_start_createtime}" - f"{sql_end_createtime}" - f"ORDER BY CreateTime ASC LIMIT ?,?" - ) - param = param + (start_index, page_size) - result = self.execute(sql, param) - if not result: - return [], [] - - result_data = (self.get_msg_detail(row) for row in result) - rdata = list(result_data) # 转为列表 - wxid_list = {d['talker'] for d in rdata} # 创建一个无重复的 wxid 列表 - - return rdata, list(wxid_list) - - @db_error - def get_date_count(self, wxid='', start_time: int = 0, end_time: int = 0, time_format='%Y-%m-%d'): - """ - 获取每日聊天记录数量,包括发送者数量、接收者数量和总数。 - """ - if not self.tables_exist("MSG"): - return {} - if isinstance(start_time, str) and start_time.isdigit(): - start_time = int(start_time) - if isinstance(end_time, str) and end_time.isdigit(): - end_time = int(end_time) - - # if start_time or end_time is not an integer and not a float, set both to 0 - if not (isinstance(start_time, (int, float)) and isinstance(end_time, (int, float))): - start_time = 0 - end_time = 0 - params = () - - sql_wxid = "AND StrTalker = ? " if wxid else "" - params = params + (wxid,) if wxid else params - - sql_time = "AND CreateTime BETWEEN ? AND ? " if start_time and end_time else "" - params = params + (start_time, end_time) if start_time and end_time else params - - sql = (f"SELECT strftime('{time_format}', CreateTime, 'unixepoch', 'localtime') AS date, " - " COUNT(*) AS total_count ," - " SUM(CASE WHEN IsSender = 1 THEN 1 ELSE 0 END) AS sender_count, " - " SUM(CASE WHEN IsSender = 0 THEN 1 ELSE 0 END) AS receiver_count " - "FROM MSG " - "WHERE StrTalker NOT LIKE '%chatroom%' " - f"{sql_wxid} {sql_time} " - f"GROUP BY date ORDER BY date ASC;") - result = self.execute(sql, params) - - if not result: - return {} - # 将查询结果转换为字典 - result_dict = {} - for row in result: - date, total_count, sender_count, receiver_count = row - result_dict[date] = { - "sender_count": sender_count, - "receiver_count": receiver_count, - "total_count": total_count - } - return result_dict - - @db_error - def get_top_talker_count(self, top: int = 10, start_time: int = 0, end_time: int = 0): - """ - 获取聊天记录数量最多的联系人,他们聊天记录数量 - """ - if not self.tables_exist("MSG"): - return {} - if isinstance(start_time, str) and start_time.isdigit(): - start_time = int(start_time) - if isinstance(end_time, str) and end_time.isdigit(): - end_time = int(end_time) - - # if start_time or end_time is not an integer and not a float, set both to 0 - if not (isinstance(start_time, (int, float)) and isinstance(end_time, (int, float))): - start_time = 0 - end_time = 0 - - sql_time = f"AND CreateTime BETWEEN {start_time} AND {end_time} " if start_time and end_time else "" - sql = ( - "SELECT StrTalker, COUNT(*) AS count," - "SUM(CASE WHEN IsSender = 1 THEN 1 ELSE 0 END) AS sender_count, " - "SUM(CASE WHEN IsSender = 0 THEN 1 ELSE 0 END) AS receiver_count " - "FROM MSG " - "WHERE StrTalker NOT LIKE '%chatroom%' " - f"{sql_time} " - "GROUP BY StrTalker ORDER BY count DESC " - f"LIMIT {top};" - ) - result = self.execute(sql) - if not result: - return {} - # 将查询结果转换为字典 - result_dict = {row[0]: {"total_count": row[1], "sender_count": row[2], "receiver_count": row[3]} for row in - result} - return result_dict - @db_error def decompress_CompressContent(data): diff --git a/pywxdump/db/dbSns.py b/pywxdump/db/dbSns.py index f4621fc..8d71e53 100644 --- a/pywxdump/db/dbSns.py +++ b/pywxdump/db/dbSns.py @@ -1,22 +1,61 @@ # -*- coding: utf-8 -*-# # ------------------------------------------------------------------------------- # Name: Sns.py -# Description: 负责处理朋友圈相关数据 +# Description: 负责处理朋友圈相关数据 软件只能看到在电脑微信浏览过的朋友圈记录 # Author: xaoyaoo # Date: 2024/04/15 # ------------------------------------------------------------------------------- -from .dbbase import DatabaseBase -from .utils import silk2audio +import json +from .dbbase import DatabaseBase +from .utils import silk2audio, xml2dict, timestamp2str + + +# FeedsV20:朋友圈的XML数据 +# CommentV20:朋友圈点赞或评论记录 +# NotificationV7:朋友圈通知 +# SnsConfigV20:一些配置信息,能读懂的是其中有你的朋友圈背景图 +# SnsGroupInfoV5:猜测是旧版微信朋友圈可见范围的可见或不可见名单 class SnsHandler(DatabaseBase): _class_name = "Sns" Media_required_tables = ["AdFeedsV8", "FeedsV20", "CommentV20", "NotificationV7", "SnsConfigV20", "SnsFailureV5", "SnsGroupInfoV5", "SnsNoNotifyV5"] - """ - FeedsV20:朋友圈的XML数据 - CommentV20:朋友圈点赞或评论记录 - NotificationV7:朋友圈通知 - SnsConfigV20:一些配置信息,能读懂的是其中有你的朋友圈背景图 - SnsGroupInfoV5:猜测是旧版微信朋友圈可见范围的可见或不可见名单 - """ + + def get_sns_feed(self): + """ + 获取朋友圈数据 + """ + sql = ("SELECT FeedId, CreateTime, FaultId, Type, UserName, Status, ExtFlag, PrivFlag, StringId, Content, " + "Reserved1, Reserved2, Reserved3, Reserved4, Reserved5, Reserved6, ExtraBuf, Reserved7 " + "FROM FeedsV20 " + "ORDER BY CreateTime DESC") + FeedsV20 = self.execute(sql) + for row in FeedsV20[2:]: + (FeedId, CreateTime, FaultId, Type, UserName, Status, ExtFlag, PrivFlag, StringId, Content, + Reserved1, Reserved2, Reserved3, Reserved4, Reserved5, Reserved6, ExtraBuf, Reserved7) = row + + Content = xml2dict(Content) if Content and Content.startswith("<") else Content + CreateTime = timestamp2str(CreateTime) + print(f"" + f"{FeedId=}\n" + f"{CreateTime=}\n" + f"{FaultId=}\n" + f"{Type=}\n" + f"{UserName=}\n" + f"{Status=}\n" + f"{ExtFlag=}\n" + f"{PrivFlag=}\n" + f"{StringId=}\n\n" + f"{json.dumps(Content,indent=4,ensure_ascii=False)}\n\n" + f"{ExtraBuf=}\n" + f"{Reserved1=}\n" + f"{Reserved2=}\n" + f"{Reserved3=}\n" + f"{Reserved4=}\n" + f"{Reserved5=}\n" + f"{Reserved6=}\n" + f"{Reserved7=}\n" + ) + + break diff --git a/pywxdump/wx_core/tools/realTime.exe b/pywxdump/wx_core/tools/realTime.exe index 648799e..7c78806 100644 Binary files a/pywxdump/wx_core/tools/realTime.exe and b/pywxdump/wx_core/tools/realTime.exe differ