pyqt支持群聊显示,合并群聊和非群聊的获取

This commit is contained in:
shuaikangzhou 2024-01-07 20:24:12 +08:00
parent 1209caa378
commit cc281fb352
15 changed files with 187 additions and 82 deletions

View File

@ -14,11 +14,7 @@ class CSVExporter(ExporterBase):
columns = ['localId', 'TalkerId', 'Type', 'SubType',
'IsSender', 'CreateTime', 'Status', 'StrContent',
'StrTime', 'Remark', 'NickName', 'Sender']
if self.contact.is_chatroom:
packagemsg = PackageMsg()
messages = packagemsg.get_package_message_by_wxid(self.contact.wxid)
else:
messages = msg_db.get_messages(self.contact.wxid)
messages = msg_db.get_messages(self.contact.wxid)
# 写入CSV文件
with open(filename, mode='w', newline='', encoding='utf-8-sig') as file:
writer = csv.writer(file)

View File

@ -1,27 +1,20 @@
import os
import shutil
import sys
import time
import traceback
from re import findall
import docx
from PyQt5.QtCore import pyqtSignal, QThread
from docx import shared
from docx.enum.table import WD_ALIGN_VERTICAL
from docx.enum.text import WD_COLOR_INDEX, WD_PARAGRAPH_ALIGNMENT
from docx.oxml.ns import qn
from app.DataBase import msg_db, hard_link_db, media_msg_db
from app.DataBase import msg_db, hard_link_db
from app.DataBase.output import ExporterBase, escape_js_and_html
from app.DataBase.package_msg import PackageMsg
from app.log import logger
from app.person import Me
from app.util import path
from app.util.compress_content import parser_reply, share_card, music_share
from app.util.emoji import get_emoji_url
from app.util.file import get_file
from app.util.image import get_image_path, get_image, get_image_abs_path
from app.util.image import get_image_abs_path
from app.util.music import get_music_path
@ -296,11 +289,7 @@ class DocxExporter(ExporterBase):
doc = docx.Document()
doc.styles['Normal'].font.name = u'Cambria'
doc.styles['Normal']._element.rPr.rFonts.set(qn('w:eastAsia'), u'宋体')
if self.contact.is_chatroom:
packagemsg = PackageMsg()
messages = packagemsg.get_package_message_by_wxid(self.contact.wxid)
else:
messages = msg_db.get_messages(self.contact.wxid)
messages = msg_db.get_messages(self.contact.wxid)
Me().save_avatar(os.path.join(f"{origin_docx_path}/avatar/{Me().wxid}.png"))
if self.contact.is_chatroom:
for message in messages:

View File

@ -276,11 +276,7 @@ class HtmlExporter(ExporterBase):
)
def export(self):
if self.contact.is_chatroom:
packagemsg = PackageMsg()
messages = packagemsg.get_package_message_by_wxid(self.contact.wxid)
else:
messages = msg_db.get_messages(self.contact.wxid)
messages = msg_db.get_messages(self.contact.wxid)
filename = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}/{self.contact.remark}.html"
file_path = './app/resources/data/template.html'
if not os.path.exists(file_path):

View File

@ -114,11 +114,7 @@ class TxtExporter(ExporterBase):
origin_docx_path = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}"
os.makedirs(origin_docx_path, exist_ok=True)
filename = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}/{self.contact.remark}.txt"
if self.contact.is_chatroom:
packagemsg = PackageMsg()
messages = packagemsg.get_package_message_by_wxid(self.contact.wxid)
else:
messages = msg_db.get_messages(self.contact.wxid)
messages = msg_db.get_messages(self.contact.wxid)
total_steps = len(messages)
with open(filename, mode='w', newline='', encoding='utf-8') as f:
for index, message in enumerate(messages):

View File

@ -93,6 +93,7 @@ class MicroMsg:
self.cursor.execute(sql, [username])
result = self.cursor.fetchone()
except sqlite3.OperationalError:
# 解决ContactLabel表不存在的问题
# lock.acquire(True)
sql = '''
SELECT UserName, Alias, Type, Remark, NickName, PYInitial, RemarkPYInitial, ContactHeadImgUrl.smallHeadImgUrl, ContactHeadImgUrl.bigHeadImgUrl,ExTraBuf,"None"

View File

@ -16,6 +16,70 @@ def is_database_exist():
return os.path.exists(db_path)
def parser_chatroom_message(messages):
from app.DataBase import micro_msg_db, misc_db
from app.util.protocbuf.msg_pb2 import MessageBytesExtra
from app.person import Contact, Me, ContactDefault
'''
获取一个群聊的聊天记录
return list
a[0]: localId,
a[1]: talkerId, 和strtalker对应的不是群聊信息发送人
a[2]: type,
a[3]: subType,
a[4]: is_sender,
a[5]: timestamp,
a[6]: status, 没啥用
a[7]: str_content,
a[8]: str_time, 格式化的时间
a[9]: msgSvrId,
a[10]: BytesExtra,
a[11]: CompressContent,
a[12]: msg_sender, ContactPC ContactDefault 类型这个才是群聊里的信息发送人不是群聊或者自己是发送者没有这个字段
'''
updated_messages = [] # 用于存储修改后的消息列表
for row in messages:
message = list(row)
if message[4] == 1: # 自己发送的就没必要解析了
message.append(Me())
updated_messages.append(tuple(message))
continue
if message[10] is None: # BytesExtra是空的跳过
message.append(ContactDefault(wxid))
updated_messages.append(tuple(message))
continue
msgbytes = MessageBytesExtra()
msgbytes.ParseFromString(message[10])
wxid = ''
for tmp in msgbytes.message2:
if tmp.field1 != 1:
continue
wxid = tmp.field2
if wxid == "": # 系统消息里面 wxid 不存在
message.append(ContactDefault(wxid))
updated_messages.append(tuple(message))
continue
contact_info_list = micro_msg_db.get_contact_by_username(wxid)
if contact_info_list is None: # 群聊中已退群的联系人不会保存在数据库里
message.append(ContactDefault(wxid))
updated_messages.append(tuple(message))
continue
contact_info = {
'UserName': contact_info_list[0],
'Alias': contact_info_list[1],
'Type': contact_info_list[2],
'Remark': contact_info_list[3],
'NickName': contact_info_list[4],
'smallHeadImgUrl': contact_info_list[7]
}
contact = Contact(contact_info)
contact.smallHeadImgBLOG = misc_db.get_avatar_buffer(contact.wxid)
contact.set_avatar(contact.smallHeadImgBLOG)
message.append(contact)
updated_messages.append(tuple(message))
return updated_messages
def singleton(cls):
_instance = {}
@ -105,7 +169,7 @@ class Msg:
result = self.cursor.fetchall()
finally:
lock.release()
return result
return parser_chatroom_message(result) if username_.__contains__('@chatroom') else result
# result.sort(key=lambda x: x[5])
# return self.add_sender(result)
@ -164,7 +228,7 @@ class Msg:
finally:
lock.release()
# result.sort(key=lambda x: x[5])
return result
return parser_chatroom_message(result) if username_.__contains__('@chatroom') else result
def get_messages_by_type(self, username_, type_, year_='all'):
if not self.open_flag:
@ -629,14 +693,15 @@ if __name__ == '__main__':
msg.init_database()
wxid = 'wxid_0o18ef858vnu22'
wxid = '24521163022@chatroom'
wxid = 'wxid_vtz9jk9ulzjt22' # si
wxid = 'wxid_vtz9jk9ulzjt22' # si
print()
from app.util import compress_content
import xml.etree.ElementTree as ET
msgs = msg.get_messages(wxid)
for msg in msgs:
if msg[2]==49 and msg[3]==5:
if msg[2] == 49 and msg[3] == 5:
xml = compress_content.decompress_CompressContent(msg[11])
root = ET.XML(xml)
appmsg = root.find('appmsg')
@ -658,4 +723,4 @@ if __name__ == '__main__':
print(thumb)
if tmp.field2 == 4:
app_logo = tmp.field2
print('logo',app_logo)
print('logo', app_logo)

View File

@ -110,7 +110,6 @@ class PackageMsg:
a[12]: msg_sender, ContactPC ContactDefault 类型这个才是群聊里的信息发送人不是群聊或者自己是发送者没有这个字段
'''
updated_messages = [] # 用于存储修改后的消息列表
chatroom_members = self.get_chatroom_member_list(chatroom_wxid)
messages = msg_db.get_messages(chatroom_wxid)
for row in messages:
message = list(row)
@ -150,7 +149,7 @@ class PackageMsg:
contact.smallHeadImgBLOG = misc_db.get_avatar_buffer(contact.wxid)
contact.set_avatar(contact.smallHeadImgBLOG)
message.append(contact)
updated_messages.append(message)
updated_messages.append(tuple(message))
return updated_messages
def get_chatroom_member_list(self, strtalker):

View File

@ -1,11 +1,12 @@
import os.path
import subprocess
import platform
from PIL import Image
from PyQt5 import QtGui
from PyQt5.QtCore import QSize, pyqtSignal, Qt, QThread
from PyQt5.QtGui import QPainter, QFont, QColor, QPixmap, QPolygon, QFontMetrics
from PyQt5.QtWidgets import QWidget, QLabel, QHBoxLayout, QSizePolicy, QVBoxLayout, QSpacerItem, \
QScrollArea, QScrollBar
QScrollArea
from app.components.scroll_bar import ScrollBar
@ -83,7 +84,7 @@ class Notice(QLabel):
def __init__(self, text, type_=3, parent=None):
super().__init__(text, parent)
self.type_ = type_
self.setFont(QFont('微软雅黑', 12))
self.setFont(QFont('微软雅黑', 10))
self.setWordWrap(True)
self.setTextInteractionFlags(Qt.TextSelectableByMouse)
self.setAlignment(Qt.AlignCenter)
@ -100,6 +101,19 @@ class Avatar(QLabel):
self.setFixedSize(QSize(45, 45))
def open_image_viewer(file_path):
system_platform = platform.system()
if system_platform == "Darwin": # macOS
subprocess.run(["open", file_path])
elif system_platform == "Windows":
subprocess.run(["start", " ", file_path], shell=True)
elif system_platform == "Linux":
subprocess.run(["xdg-open", file_path])
else:
print("Unsupported platform")
class OpenImageThread(QThread):
def __init__(self, image_path):
super().__init__()
@ -107,8 +121,7 @@ class OpenImageThread(QThread):
def run(self) -> None:
if os.path.exists(self.image_path):
image = Image.open(self.image_path)
image.show()
open_image_viewer(self.image_path)
class ImageMessage(QLabel):
@ -121,6 +134,9 @@ class ImageMessage(QLabel):
self.image = QLabel(self)
self.max_width = max_width
self.max_height = max_height
# self.setFixedSize(self.max_width,self.max_height)
self.setMaximumWidth(self.max_width)
self.setMaximumHeight(self.max_height)
if isinstance(image, str):
pixmap = QPixmap(image)
self.image_path = image
@ -129,8 +145,7 @@ class ImageMessage(QLabel):
self.set_image(pixmap)
if image_link:
self.image_path = image_link
self.setMaximumWidth(self.max_width)
self.setMaximumHeight(self.max_height)
if is_send:
self.setAlignment(Qt.AlignCenter | Qt.AlignRight)
# self.setScaledContents(True)
@ -151,7 +166,7 @@ class ImageMessage(QLabel):
class BubbleMessage(QWidget):
def __init__(self, str_content, avatar, Type, is_send=False, parent=None):
def __init__(self, str_content, avatar, Type,is_send=False, display_name=None, parent=None):
super().__init__(parent)
self.isSend = is_send
# self.set
@ -173,17 +188,30 @@ class BubbleMessage(QWidget):
self.message = ImageMessage(str_content, is_send)
else:
raise ValueError("未知的消息类型")
if display_name:
label_name = QLabel(display_name,self)
if is_send:
label_name.setAlignment(Qt.AlignRight)
vlayout = QVBoxLayout()
vlayout.setSpacing(0)
vlayout.addWidget(label_name)
vlayout.addWidget(self.message)
self.spacerItem = QSpacerItem(45 + 6, 45, QSizePolicy.Expanding, QSizePolicy.Minimum)
if is_send:
layout.addItem(self.spacerItem)
layout.addWidget(self.message, 1)
if display_name:
layout.addLayout(vlayout,1)
else:
layout.addWidget(self.message, 1)
layout.addWidget(triangle, 0, Qt.AlignTop | Qt.AlignLeft)
layout.addWidget(self.avatar, 0, Qt.AlignTop | Qt.AlignLeft)
else:
layout.addWidget(self.avatar, 0, Qt.AlignTop | Qt.AlignRight)
layout.addWidget(triangle, 0, Qt.AlignTop | Qt.AlignRight)
layout.addWidget(self.message, 1)
if display_name:
layout.addLayout(vlayout,1)
else:
layout.addWidget(self.message, 1)
layout.addItem(self.spacerItem)
self.setLayout(layout)
@ -206,8 +234,6 @@ class ScrollArea(QScrollArea):
)
class ChatWidget(QWidget):
def __init__(self):
super().__init__()

View File

@ -47,8 +47,9 @@ class Person:
os.makedirs('./data/avatar', exist_ok=True)
save_path = os.path.join(f'data/avatar/', self.wxid + '.png')
self.avatar_path = save_path
self.avatar.save(save_path)
print('保存头像', save_path)
if not os.path.exists(save_path):
self.avatar.save(save_path)
print('保存头像', save_path)
@singleton

View File

@ -36,6 +36,8 @@ class ChatInfo(QWidget):
self.setLayout(self.vBoxLayout)
def show_chats(self):
# Me().save_avatar()
# self.contact.save_avatar()
self.show_chat_thread = ShowChatThread(self.contact)
self.show_chat_thread.showSingal.connect(self.add_message)
self.show_chat_thread.finishSingal.connect(self.show_finish)
@ -79,6 +81,29 @@ class ChatInfo(QWidget):
return True
return False
def get_avatar_path(self, is_send, message, is_absolute_path=False) -> str:
if self.contact.is_chatroom:
avatar = message[12].smallHeadImgUrl
else:
avatar = Me().smallHeadImgUrl if is_send else self.contact.smallHeadImgUrl
if is_absolute_path:
if self.contact.is_chatroom:
# message[12].save_avatar()
avatar = message[12].avatar
else:
avatar = Me().avatar if is_send else self.contact.avatar
return avatar
def get_display_name(self, is_send, message) -> str:
if self.contact.is_chatroom:
if is_send:
display_name = Me().name
else:
display_name = message[12].remark
else:
display_name = None
return display_name
def add_message(self, message):
try:
type_ = message[2]
@ -86,7 +111,8 @@ class ChatInfo(QWidget):
str_time = message[8]
# print(type_, type(type_))
is_send = message[4]
avatar = Me().avatar if is_send else self.contact.avatar
avatar = self.get_avatar_path(is_send, message,True)
display_name = self.get_display_name(is_send, message)
timestamp = message[5]
BytesExtra = message[10]
if type_ == 1:
@ -98,7 +124,8 @@ class ChatInfo(QWidget):
str_content,
avatar,
type_,
is_send
is_send,
display_name=display_name
)
self.chat_window.add_message_item(bubble_message, 0)
elif type_ == 3:
@ -107,7 +134,7 @@ class ChatInfo(QWidget):
time_message = Notice(self.last_str_time)
self.last_str_time = str_time
self.chat_window.add_message_item(time_message, 0)
image_path = hard_link_db.get_image(content=str_content,bytesExtra=BytesExtra, thumb=False)
image_path = hard_link_db.get_image(content=str_content, bytesExtra=BytesExtra, thumb=False)
image_path = get_abs_path(image_path)
bubble_message = BubbleMessage(
image_path,
@ -132,7 +159,7 @@ class ChatInfo(QWidget):
self.chat_window.add_message_item(bubble_message, 0)
elif type_ == 10000:
str_content = str_content.lstrip('<revokemsg>').rstrip('</revokemsg>')
message = Notice(str_content )
message = Notice(str_content)
self.chat_window.add_message_item(message, 0)
except:
print(message)

View File

@ -79,10 +79,7 @@ class MainWinController(QMainWindow, mainwindow.Ui_MainWindow,QCursorGif):
self.outputThread0 = None
self.outputThread = None
self.setupUi(self)
# 设置忙碌光标图片数组
self.initCursor([':/icons/icons/Cursors/%d.png' %
i for i in range(8)])
self.setCursorTimeout(100)
# self.setWindowIcon(Icon.MainWindow_Icon)
pixmap = QPixmap(Icon.logo_ico_path)
icon = QIcon(pixmap)
@ -97,6 +94,14 @@ class MainWinController(QMainWindow, mainwindow.Ui_MainWindow,QCursorGif):
self.label = QLabel(self)
self.label.setGeometry((self.width() - 300) // 2, (self.height() - 100) // 2, 300, 100)
self.label.setPixmap(QPixmap(':/icons/icons/loading.svg'))
self.menu_output.setIcon(Icon.Output)
self.action_output_CSV.setIcon(Icon.ToCSV)
self.action_output_CSV.triggered.connect(self.output)
self.action_output_contacts.setIcon(Icon.Output)
self.action_output_contacts.triggered.connect(self.output)
self.action_batch_export.setIcon(Icon.Output)
self.action_batch_export.triggered.connect(self.output)
self.action_desc.setIcon(Icon.Help_Icon)
def load_data(self, flag=True):
if os.path.exists('./app/data/info.json'):
@ -119,15 +124,15 @@ class MainWinController(QMainWindow, mainwindow.Ui_MainWindow,QCursorGif):
)
def init_ui(self):
# 设置忙碌光标图片数组
self.initCursor([':/icons/icons/Cursors/%d.png' %
i for i in range(8)])
self.setCursorTimeout(100)
self.startBusy()
self.menu_output.setIcon(Icon.Output)
self.action_output_CSV.setIcon(Icon.ToCSV)
self.action_output_CSV.triggered.connect(self.output)
self.action_output_contacts.setIcon(Icon.Output)
self.action_output_contacts.triggered.connect(self.output)
self.action_batch_export.setIcon(Icon.Output)
self.action_batch_export.triggered.connect(self.output)
self.action_desc.setIcon(Icon.Help_Icon)
self.action_help_contact.triggered.connect(
lambda: QDesktopServices.openUrl(QUrl("https://blog.lc044.love/post/5")))
self.action_help_chat.triggered.connect(

View File

@ -130,7 +130,7 @@ class DecryptControl(QWidget, decryptUi.Ui_Dialog, QCursorGif):
return
if self.info.get('key') == 'None':
QMessageBox.critical(self, "错误",
"密钥错误\n将软件放在桌面上试试\n如果还不可以的话我也我能为力,您可以等待后续版本解决该问题")
"密钥错误\n请查看教程解决相关问题")
close_db()
self.thread2 = DecryptThread(db_dir, self.info['key'])
self.thread2.maxNumSignal.connect(self.setProgressBarMaxNum)
@ -232,11 +232,13 @@ class DecryptThread(QThread):
try:
shutil.copy2("app/DataBase/Msg/MSG0.db", target_database) # 使用一个数据库文件作为模板
except FileNotFoundError:
logger.error(traceback.format_exc())
self.errorSignal.emit(True)
# 合并数据库
try:
merge_databases(source_databases, target_database)
except FileNotFoundError:
logger.error(traceback.format_exc())
QMessageBox.critical("错误", "数据库不存在\n请检查微信版本是否为最新")
# 音频数据库文件
@ -248,11 +250,13 @@ class DecryptThread(QThread):
try:
shutil.copy2("app/DataBase/Msg/MediaMSG0.db", target_database) # 使用一个数据库文件作为模板
except FileNotFoundError:
logger.error(traceback.format_exc())
self.errorSignal.emit(True)
# 合并数据库
try:
merge_MediaMSG_databases(source_databases, target_database)
except FileNotFoundError:
logger.error(traceback.format_exc())
QMessageBox.critical("错误", "数据库不存在\n请检查微信版本是否为最新")
self.okSignal.emit('ok')
# self.signal.emit('100')

26
main.py
View File

@ -3,19 +3,6 @@ import sys
import time
import traceback
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt
from app.DataBase import close_db
from app.log import logger
from app.ui import mainview
from app.ui.tool.pc_decrypt import pc_decrypt
from app.config import version
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("WeChatReport")
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
widget = None
@ -35,6 +22,19 @@ def excepthook(exc_type, exc_value, traceback_):
# 设置 excepthook
sys.excepthook = excepthook
from PyQt5.QtGui import QFont
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt
from app.DataBase import close_db
from app.log import logger
from app.ui import mainview
from app.ui.tool.pc_decrypt import pc_decrypt
from app.config import version
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("WeChatReport")
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
class ViewController(QWidget):

View File

@ -272,6 +272,7 @@ python main.py
- [STDquantum](https://github.com/STDquantum)
- [xuanli](https://github.com/xuanli)
- [无名路人](https://github.com/wumingluren)
如果您提供赞助并希望出现在赞助者名单中,请在提交赞助时提供您的 GitHub 用户名或其他相关信息。

View File

@ -7,7 +7,6 @@ silk-python
pyaudio
fuzzywuzzy
python-Levenshtein
Pillow==10.1.0
requests
flask==3.0.0
pyecharts==2.0.1