From 9ca75c73e2880855574bfa19a402af8e96803789 Mon Sep 17 00:00:00 2001
From: shuaikangzhou <863909694@qq.com>
Date: Wed, 13 Dec 2023 22:12:50 +0800
Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E5=A4=9A=E6=AC=A1=E8=A7=A3?=
=?UTF-8?q?=E5=AF=86=E5=AF=BC=E8=87=B4=E7=9A=84=E6=95=B0=E6=8D=AE=E9=87=8D?=
=?UTF-8?q?=E5=A4=8D=E9=97=AE=E9=A2=98?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
app/DataBase/msg.py | 14 +-
app/DataBase/output_pc.py | 2 +-
app/{person_pc.py => person.py} | 0
app/ui/Icon.py | 28 +++
app/ui/__init__.py | 0
app/ui/chat/__init__.py | 1 +
app/ui/chat/chatInfoUi.py | 48 +++++
app/ui/chat/chatUi.py | 74 ++++++++
app/ui/chat/chat_info.py | 160 ++++++++++++++++
app/ui/chat/chat_window.py | 165 ++++++++++++++++
app/ui/contact/__init__.py | 1 +
app/ui/contact/contactInfo.py | 162 ++++++++++++++++
app/ui/contact/contactInfoUi.py | 107 +++++++++++
app/ui/contact/contactUi.py | 74 ++++++++
app/ui/contact/contact_window.py | 155 ++++++++++++++++
app/ui/contact/export_dialog.py | 105 +++++++++++
app/ui/contact/userinfo/__init__.py | 9 +
app/ui/contact/userinfo/userinfo.py | 15 ++
app/ui/contact/userinfo/userinfoUi.py | 123 ++++++++++++
app/ui/mainview.py | 250 +++++++++++++++++++++++++
app/ui/mainwindow.py | 179 ++++++++++++++++++
app/ui/tool/__init__.py | 1 +
app/ui/tool/pc_decrypt/__init__.py | 3 +
app/ui/tool/pc_decrypt/decryptUi.py | 197 ++++++++++++++++++++
app/ui/tool/pc_decrypt/pc_decrypt.py | 258 ++++++++++++++++++++++++++
app/ui/tool/toolUI.py | 83 +++++++++
app/ui/tool/tool_window.py | 89 +++++++++
app/util/path.py | 2 +-
app/web_ui/web.py | 8 +-
29 files changed, 2294 insertions(+), 19 deletions(-)
rename app/{person_pc.py => person.py} (100%)
create mode 100644 app/ui/Icon.py
create mode 100644 app/ui/__init__.py
create mode 100644 app/ui/chat/__init__.py
create mode 100644 app/ui/chat/chatInfoUi.py
create mode 100644 app/ui/chat/chatUi.py
create mode 100644 app/ui/chat/chat_info.py
create mode 100644 app/ui/chat/chat_window.py
create mode 100644 app/ui/contact/__init__.py
create mode 100644 app/ui/contact/contactInfo.py
create mode 100644 app/ui/contact/contactInfoUi.py
create mode 100644 app/ui/contact/contactUi.py
create mode 100644 app/ui/contact/contact_window.py
create mode 100644 app/ui/contact/export_dialog.py
create mode 100644 app/ui/contact/userinfo/__init__.py
create mode 100644 app/ui/contact/userinfo/userinfo.py
create mode 100644 app/ui/contact/userinfo/userinfoUi.py
create mode 100644 app/ui/mainview.py
create mode 100644 app/ui/mainwindow.py
create mode 100644 app/ui/tool/__init__.py
create mode 100644 app/ui/tool/pc_decrypt/__init__.py
create mode 100644 app/ui/tool/pc_decrypt/decryptUi.py
create mode 100644 app/ui/tool/pc_decrypt/pc_decrypt.py
create mode 100644 app/ui/tool/toolUI.py
create mode 100644 app/ui/tool/tool_window.py
diff --git a/app/DataBase/msg.py b/app/DataBase/msg.py
index 0f4dea43..ecbfab41 100644
--- a/app/DataBase/msg.py
+++ b/app/DataBase/msg.py
@@ -63,7 +63,6 @@ def get_messages(self, username_):
select localId,TalkerId,Type,SubType,IsSender,CreateTime,Status,StrContent,strftime('%Y-%m-%d %H:%M:%S',CreateTime,'unixepoch','localtime') as StrTime,MsgSvrID,BytesExtra,CompressContent
from MSG
where StrTalker=?
- group by MsgSvrID
order by CreateTime
'''
try:
@@ -79,7 +78,6 @@ def get_messages_all(self):
sql = '''
select localId,TalkerId,Type,SubType,IsSender,CreateTime,Status,StrContent,strftime('%Y-%m-%d %H:%M:%S',CreateTime,'unixepoch','localtime') as StrTime,MsgSvrID,BytesExtra,StrTalker,Reserved1,CompressContent
from MSG
- group by MsgSvrID
order by CreateTime
'''
if not self.open_flag:
@@ -116,7 +114,6 @@ def get_message_by_num(self, username_, local_id):
select localId,TalkerId,Type,SubType,IsSender,CreateTime,Status,StrContent,strftime('%Y-%m-%d %H:%M:%S',CreateTime,'unixepoch','localtime') as StrTime,MsgSvrID,BytesExtra,CompressContent
from MSG
where StrTalker = ? and localId < ?
- group by MsgSvrID
order by CreateTime desc
limit 20
'''
@@ -142,7 +139,6 @@ def get_messages_by_type(self, username_, type_, is_Annual_report_=False, year_=
select localId,TalkerId,Type,SubType,IsSender,CreateTime,Status,StrContent,strftime('%Y-%m-%d %H:%M:%S',CreateTime,'unixepoch','localtime') as StrTime,MsgSvrID,BytesExtra,CompressContent
from MSG
where StrTalker=? and Type=? and strftime('%Y',CreateTime,'unixepoch','localtime') = ?
- group by MsgSvrID
order by CreateTime
'''
else:
@@ -150,7 +146,6 @@ def get_messages_by_type(self, username_, type_, is_Annual_report_=False, year_=
select localId,TalkerId,Type,SubType,IsSender,CreateTime,Status,StrContent,strftime('%Y-%m-%d %H:%M:%S',CreateTime,'unixepoch','localtime') as StrTime,MsgSvrID,BytesExtra,CompressContent
from MSG
where StrTalker=? and Type=?
- group by MsgSvrID
order by CreateTime
'''
try:
@@ -171,7 +166,6 @@ def get_messages_by_keyword(self, username_, keyword, num=5, max_len=10):
select localId,TalkerId,Type,SubType,IsSender,CreateTime,Status,StrContent,strftime('%Y-%m-%d %H:%M:%S',CreateTime,'unixepoch','localtime') as StrTime,MsgSvrID,BytesExtra
from MSG
where StrTalker=? and Type=1 and LENGTH(StrContent) and StrContent like ?
- group by MsgSvrID
order by CreateTime desc
'''
temp = []
@@ -192,7 +186,6 @@ def get_messages_by_keyword(self, username_, keyword, num=5, max_len=10):
select localId,TalkerId,Type,SubType,IsSender,CreateTime,Status,StrContent,strftime('%Y-%m-%d %H:%M:%S',CreateTime,'unixepoch','localtime') as StrTime,MsgSvrID
from MSG
where localId > ? and StrTalker=? and Type=1 and IsSender=?
- group by MsgSvrID
limit 1
'''
self.cursor.execute(sql, [local_id, username_, 1 - is_send])
@@ -213,6 +206,7 @@ def get_messages_by_keyword(self, username_, keyword, num=5, max_len=10):
('', '', ['', ''], ''),
('', '', '', '')
))
+ print(keyword,res)
return res
def get_messages_by_days(self, username_, is_Annual_report_=False, year_='2023'):
@@ -223,7 +217,6 @@ def get_messages_by_days(self, username_, is_Annual_report_=False, year_='2023')
SELECT MsgSvrID, CreateTime
FROM MSG
WHERE StrTalker = ? AND strftime('%Y', CreateTime, 'unixepoch', 'localtime') = ?
- GROUP BY MsgSvrID
)
group by days
'''
@@ -234,7 +227,6 @@ def get_messages_by_days(self, username_, is_Annual_report_=False, year_='2023')
SELECT MsgSvrID, CreateTime
FROM MSG
WHERE StrTalker = ?
- GROUP BY MsgSvrID
)
group by days
'''
@@ -260,7 +252,6 @@ def get_messages_by_month(self, username_, is_Annual_report_=False, year_='2023'
SELECT MsgSvrID, CreateTime
FROM MSG
WHERE StrTalker = ? AND strftime('%Y', CreateTime, 'unixepoch', 'localtime') = ?
- GROUP BY MsgSvrID
)
group by days
'''
@@ -271,7 +262,6 @@ def get_messages_by_month(self, username_, is_Annual_report_=False, year_='2023'
SELECT MsgSvrID, CreateTime
FROM MSG
WHERE StrTalker = ?
- GROUP BY MsgSvrID
)
group by days
'''
@@ -300,7 +290,6 @@ def get_messages_by_hour(self, username_, is_Annual_report_=False, year_='2023')
SELECT MsgSvrID, CreateTime
FROM MSG
where StrTalker = ? and strftime('%Y',CreateTime,'unixepoch','localtime') = ?
- GROUP BY MsgSvrID
)
group by hours
'''
@@ -311,7 +300,6 @@ def get_messages_by_hour(self, username_, is_Annual_report_=False, year_='2023')
SELECT MsgSvrID, CreateTime
FROM MSG
where StrTalker = ?
- GROUP BY MsgSvrID
)
group by hours
'''
diff --git a/app/DataBase/output_pc.py b/app/DataBase/output_pc.py
index 2f06831c..2209dead 100644
--- a/app/DataBase/output_pc.py
+++ b/app/DataBase/output_pc.py
@@ -9,7 +9,7 @@
from .package_msg import PackageMsg
from ..DataBase import hard_link_db
from ..DataBase import media_msg_db
-from ..person_pc import MePC
+from ..person import MePC
from ..util import path
import shutil
diff --git a/app/person_pc.py b/app/person.py
similarity index 100%
rename from app/person_pc.py
rename to app/person.py
diff --git a/app/ui/Icon.py b/app/ui/Icon.py
new file mode 100644
index 00000000..4fbff96b
--- /dev/null
+++ b/app/ui/Icon.py
@@ -0,0 +1,28 @@
+from PyQt5.QtGui import QIcon
+
+from app.resources import resource_rc
+
+var = resource_rc.qt_resource_name
+
+
+class Icon:
+ Default_avatar_path = ':/icons/icons/default_avatar.svg'
+ Default_image_path = ':/icons/icons/404.png'
+ MainWindow_Icon = QIcon(':/icons/icons/logo.svg')
+ Default_avatar = QIcon(Default_avatar_path)
+ Output = QIcon(':/icons/icons/output.svg')
+ Back = QIcon(':/icons/icons/back.svg')
+ ToDocx = QIcon(':/icons/icons/word.svg')
+ ToCSV = QIcon(':/icons/icons/csv.svg')
+ ToHTML = QIcon(':/icons/icons/html.svg')
+ ToTXT = QIcon(':/icons/icons/txt.svg')
+ Chat_Icon = QIcon(':/icons/icons/chat.svg')
+ Contact_Icon = QIcon(':/icons/icons/contact.svg')
+ MyInfo_Icon = QIcon(':/icons/icons/myinfo.svg')
+ Annual_Report_Icon = QIcon(':/icons/icons/annual_report.svg')
+ Analysis_Icon = QIcon(':/icons/icons/analysis.svg')
+ Emotion_Icon = QIcon(':/icons/icons/emotion.svg')
+ Search_Icon = QIcon(':/icons/icons/search.svg')
+ Tool_Icon = QIcon(':/icons/icons/tool.svg')
+ Home_Icon = QIcon(':/icons/icons/home.svg')
+ Help_Icon = QIcon(':/icons/icons/help.svg')
diff --git a/app/ui/__init__.py b/app/ui/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/app/ui/chat/__init__.py b/app/ui/chat/__init__.py
new file mode 100644
index 00000000..00e557bb
--- /dev/null
+++ b/app/ui/chat/__init__.py
@@ -0,0 +1 @@
+from .chat_window import ChatWindow
diff --git a/app/ui/chat/chatInfoUi.py b/app/ui/chat/chatInfoUi.py
new file mode 100644
index 00000000..62c03c5b
--- /dev/null
+++ b/app/ui/chat/chatInfoUi.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'chatInfoUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtWidgets
+
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ Form.setObjectName("Form")
+ self.verticalLayout = QtWidgets.QVBoxLayout(Form)
+ self.verticalLayout.setContentsMargins(0, 0, 0, 0)
+ self.verticalLayout.setSpacing(0)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.frame = QtWidgets.QFrame(Form)
+ self.frame.setFrameShape(QtWidgets.QFrame.NoFrame)
+ self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
+ self.frame.setObjectName("frame")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.label_reamrk = QtWidgets.QLabel(self.frame)
+ self.label_reamrk.setObjectName("label_reamrk")
+ self.horizontalLayout_2.addWidget(self.label_reamrk)
+ spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_2.addItem(spacerItem)
+ self.toolButton = QtWidgets.QToolButton(self.frame)
+ self.toolButton.setObjectName("toolButton")
+ self.horizontalLayout_2.addWidget(self.toolButton)
+ self.verticalLayout_2.addLayout(self.horizontalLayout_2)
+
+ self.verticalLayout.addWidget(self.frame)
+
+ self.retranslateUi(Form)
+ QtCore.QMetaObject.connectSlotsByName(Form)
+
+ def retranslateUi(self, Form):
+ _translate = QtCore.QCoreApplication.translate
+ Form.setWindowTitle(_translate("Form", "Form"))
+ self.label_reamrk.setText(_translate("Form", "TextLabel"))
+ self.toolButton.setText(_translate("Form", "..."))
diff --git a/app/ui/chat/chatUi.py b/app/ui/chat/chatUi.py
new file mode 100644
index 00000000..ddf83ecd
--- /dev/null
+++ b/app/ui/chat/chatUi.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'chatUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtWidgets
+
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ Form.setObjectName("Form")
+ Form.resize(840, 752)
+ Form.setStyleSheet("background: rgb(240, 240, 240);")
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout(Form)
+ self.horizontalLayout_2.setSpacing(6)
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout()
+ self.verticalLayout_2.setSpacing(6)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.verticalLayout = QtWidgets.QVBoxLayout()
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.label = QtWidgets.QLabel(Form)
+ self.label.setText("")
+ self.label.setObjectName("label")
+ self.horizontalLayout.addWidget(self.label)
+ self.lineEdit = QtWidgets.QLineEdit(Form)
+ self.lineEdit.setMinimumSize(QtCore.QSize(200, 30))
+ self.lineEdit.setMaximumSize(QtCore.QSize(200, 16777215))
+ self.lineEdit.setStyleSheet("background:transparent;\n"
+ "border-radius:5px;\n"
+ "border-top: 0px solid #b2e281;\n"
+ "border-bottom: 0px solid #b2e281;\n"
+ "border-right: 0px solid #b2e281;\n"
+ "border-left: 0px solid #b2e281;\n"
+ "border-style:outset;\n"
+ "background-color:rgb(226,226,226);\n"
+ " ")
+ self.lineEdit.setCursorMoveStyle(QtCore.Qt.VisualMoveStyle)
+ self.lineEdit.setObjectName("lineEdit")
+ self.horizontalLayout.addWidget(self.lineEdit)
+ self.label_2 = QtWidgets.QLabel(Form)
+ self.label_2.setMinimumSize(QtCore.QSize(30, 0))
+ self.label_2.setText("")
+ self.label_2.setObjectName("label_2")
+ self.horizontalLayout.addWidget(self.label_2)
+ self.verticalLayout.addLayout(self.horizontalLayout)
+ self.verticalLayout_2.addLayout(self.verticalLayout)
+ self.listWidget = QtWidgets.QListWidget(Form)
+ self.listWidget.setMinimumSize(QtCore.QSize(250, 0))
+ self.listWidget.setMaximumSize(QtCore.QSize(250, 16777215))
+ self.listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.listWidget.setObjectName("listWidget")
+ self.verticalLayout_2.addWidget(self.listWidget)
+ self.verticalLayout_2.setStretch(1, 1)
+ self.horizontalLayout_2.addLayout(self.verticalLayout_2)
+ self.stackedWidget = QtWidgets.QStackedWidget(Form)
+ self.stackedWidget.setObjectName("stackedWidget")
+ self.horizontalLayout_2.addWidget(self.stackedWidget)
+ self.horizontalLayout_2.setStretch(1, 1)
+
+ self.retranslateUi(Form)
+ self.stackedWidget.setCurrentIndex(-1)
+ QtCore.QMetaObject.connectSlotsByName(Form)
+
+ def retranslateUi(self, Form):
+ _translate = QtCore.QCoreApplication.translate
+ Form.setWindowTitle(_translate("Form", "Form"))
diff --git a/app/ui/chat/chat_info.py b/app/ui/chat/chat_info.py
new file mode 100644
index 00000000..ab29329c
--- /dev/null
+++ b/app/ui/chat/chat_info.py
@@ -0,0 +1,160 @@
+import traceback
+
+from PyQt5.QtCore import QThread, pyqtSignal
+from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QHBoxLayout
+
+from app.DataBase import msg_db, hard_link_db
+from app.components.bubble_message import BubbleMessage, ChatWidget, Notice
+from app.person import MePC
+from app.util import get_abs_path
+from app.util.emoji import get_emoji
+
+
+class ChatInfo(QWidget):
+ def __init__(self, contact, parent=None):
+ super().__init__(parent)
+ self.last_timestamp = 0
+ self.last_str_time = ''
+ self.last_pos = 0
+ self.contact = contact
+ self.init_ui()
+ self.show_chats()
+
+ def init_ui(self):
+ self.label_reamrk = QLabel(self.contact.remark)
+
+ self.hBoxLayout = QHBoxLayout()
+ self.hBoxLayout.addWidget(self.label_reamrk)
+
+ self.vBoxLayout = QVBoxLayout()
+ self.vBoxLayout.setSpacing(0)
+ self.vBoxLayout.addLayout(self.hBoxLayout)
+
+ self.chat_window = ChatWidget()
+ self.chat_window.scrollArea.verticalScrollBar().valueChanged.connect(self.verticalScrollBar)
+ self.vBoxLayout.addWidget(self.chat_window)
+ self.setLayout(self.vBoxLayout)
+
+ def show_chats(self):
+ 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)
+ # self.show_chat_thread.start()
+
+ def show_finish(self, ok):
+ self.setScrollBarPos()
+ self.show_chat_thread.quit()
+
+ def verticalScrollBar(self, pos):
+ """
+ 滚动条到0之后自动更新聊天记录
+ :param pos:
+ :return:
+ """
+ # print(pos)
+ if pos > 0:
+ return
+
+ # 记录当前滚动条最大值
+ self.last_pos = self.chat_window.verticalScrollBar().maximum()
+ self.update_history_messages()
+
+ def update_history_messages(self):
+ self.show_chat_thread.start()
+
+ def setScrollBarPos(self):
+ """
+ 将滚动条位置设置为上次看到的地方
+ :param pos:
+ :return:
+ """
+ self.chat_window.update()
+ self.chat_window.show()
+ pos = self.chat_window.verticalScrollBar().maximum() - self.last_pos
+ self.chat_window.set_scroll_bar_value(pos)
+
+ def is_5_min(self, timestamp):
+ if abs(timestamp - self.last_timestamp) > 300:
+ self.last_timestamp = timestamp
+ return True
+ return False
+
+ def add_message(self, message):
+ try:
+ type_ = message[2]
+ str_content = message[7]
+ str_time = message[8]
+ # print(type_, type(type_))
+ is_send = message[4]
+ avatar = MePC().avatar if is_send else self.contact.avatar
+ timestamp = message[5]
+ BytesExtra = message[10]
+ if type_ == 1:
+ if self.is_5_min(timestamp):
+ time_message = Notice(self.last_str_time)
+ self.last_str_time = str_time
+ self.chat_window.add_message_item(time_message, 0)
+ bubble_message = BubbleMessage(
+ str_content,
+ avatar,
+ type_,
+ is_send
+ )
+ self.chat_window.add_message_item(bubble_message, 0)
+ elif type_ == 3:
+ # return
+ if self.is_5_min(timestamp):
+ 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 = get_abs_path(image_path)
+ bubble_message = BubbleMessage(
+ image_path,
+ avatar,
+ type_,
+ is_send
+ )
+ self.chat_window.add_message_item(bubble_message, 0)
+ elif type_ == 47:
+ return
+ if self.is_5_min(timestamp):
+ time_message = Notice(self.last_str_time)
+ self.last_str_time = str_time
+ self.chat_window.add_message_item(time_message, 0)
+ image_path = get_emoji(str_content, thumb=True)
+ bubble_message = BubbleMessage(
+ image_path,
+ avatar,
+ 3,
+ is_send
+ )
+ self.chat_window.add_message_item(bubble_message, 0)
+ elif type_ == 10000:
+ str_content = str_content.lstrip('').rstrip('')
+ message = Notice(str_content )
+ self.chat_window.add_message_item(message, 0)
+ except:
+ print(message)
+ traceback.print_exc()
+
+
+class ShowChatThread(QThread):
+ showSingal = pyqtSignal(tuple)
+ finishSingal = pyqtSignal(int)
+ msg_id = 0
+
+ # heightSingal = pyqtSignal(int)
+ def __init__(self, contact):
+ super().__init__()
+ self.last_message_id = 9999999999
+ self.wxid = contact.wxid
+
+ def run(self) -> None:
+ messages = msg_db.get_message_by_num(self.wxid, self.last_message_id)
+ if messages:
+ self.last_message_id = messages[-1][0]
+ for message in messages:
+ self.showSingal.emit(message)
+ self.msg_id += 1
+ self.finishSingal.emit(1)
diff --git a/app/ui/chat/chat_window.py b/app/ui/chat/chat_window.py
new file mode 100644
index 00000000..59921bf2
--- /dev/null
+++ b/app/ui/chat/chat_window.py
@@ -0,0 +1,165 @@
+from PyQt5.QtCore import QThread, pyqtSignal
+from PyQt5.QtWidgets import QWidget, QMessageBox, QAction, QLineEdit
+
+from app.DataBase import micro_msg_db, misc_db, msg_db
+from app.components import ContactQListWidgetItem
+from app.person import ContactPC
+from app.ui.Icon import Icon
+from app.util import search
+from .chatUi import Ui_Form
+from .chat_info import ChatInfo
+
+# 美化样式表
+Stylesheet = """
+
+/*去掉item虚线边框*/
+QListWidget, QListView, QTreeWidget, QTreeView {
+ outline: 0px;
+ border:none;
+ background-color:rgb(240,240,240)
+}
+/*设置左侧选项的最小最大宽度,文字颜色和背景颜色*/
+QListWidget {
+ min-width: 250px;
+ max-width: 250px;
+ min-height: 80px;
+ max-height: 1200px;
+ color: black;
+ border:none;
+}
+QListWidget::item{
+ height:60px;
+ width:250px;
+}
+/*被选中时的背景颜色和左边框颜色*/
+QListWidget::item:selected {
+ background: rgb(204, 204, 204);
+ border-bottom: 2px solid rgb(9, 187, 7);
+ border-left:none;
+ color: black;
+ font-weight: bold;
+}
+/*鼠标悬停颜色*/
+HistoryPanel::item:hover {
+ background: rgb(52, 52, 52);
+}
+"""
+
+
+class ChatWindow(QWidget, Ui_Form):
+ load_finish_signal = pyqtSignal(bool)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.show_thread = None
+ self.setupUi(self)
+ self.ok_flag = False
+ self.setStyleSheet(Stylesheet)
+ self.contacts = [[], []]
+ self.init_ui()
+ self.show_chats()
+ self.visited = set()
+
+ def init_ui(self):
+ search_action = QAction(self.lineEdit)
+ search_action.setIcon(Icon.Search_Icon)
+ self.lineEdit.addAction(search_action, QLineEdit.LeadingPosition)
+ self.lineEdit.returnPressed.connect(self.search_contact)
+ self.listWidget.clear()
+ self.listWidget.currentRowChanged.connect(self.setCurrentIndex)
+ self.listWidget.setCurrentRow(0)
+ self.stackedWidget.setCurrentIndex(0)
+
+ def show_chats(self):
+ # return
+ if self.ok_flag:
+ return
+ msg_db.init_database()
+ micro_msg_db.init_database()
+ if not msg_db.open_flag:
+ QMessageBox.critical(self, "错误", "数据库不存在\n请先解密数据库")
+ self.show_thread = ShowThread()
+ self.show_thread.load_finish_signal.connect(self.load_finish_signal)
+ self.show_thread.start()
+ return
+ self.show_thread = ShowContactThread()
+ self.show_thread.showSingal.connect(self.show_chat)
+ self.show_thread.load_finish_signal.connect(self.stop_loading)
+ self.show_thread.start()
+ self.ok_flag = True
+
+ def search_contact(self):
+ content = self.lineEdit.text()
+ if not content:
+ return
+ index = self.search_contact_index(content)
+ self.select_contact_by_index(index)
+
+ def search_contact_index(self, content: str) -> int:
+ return search.search_by_content(content, self.contacts)
+
+ def select_contact_by_index(self, index):
+ self.stackedWidget.setCurrentIndex(index)
+ self.listWidget.setCurrentRow(index)
+
+ def show_chat(self, contact):
+ self.contacts[0].append(contact.remark)
+ self.contacts[1].append(contact.nickName)
+ contact_item = ContactQListWidgetItem(contact.remark, contact.smallHeadImgUrl, contact.smallHeadImgBLOG)
+ self.listWidget.addItem(contact_item)
+ self.listWidget.setItemWidget(contact_item, contact_item.widget)
+ chat_info_window = ChatInfo(contact)
+ self.stackedWidget.addWidget(chat_info_window)
+
+ def setCurrentIndex(self, row):
+ # print(row)
+ self.stackedWidget.setCurrentIndex(row)
+ if row not in self.visited:
+ chat_info_window = self.stackedWidget.currentWidget()
+ chat_info_window.update_history_messages()
+ self.visited.add(row)
+
+ def stop_loading(self, a0):
+ # self.label.setVisible(False)
+ self.load_finish_signal.emit(True)
+
+
+class ShowContactThread(QThread):
+ showSingal = pyqtSignal(ContactPC)
+ load_finish_signal = pyqtSignal(bool)
+
+ # heightSingal = pyqtSignal(int)
+ def __init__(self):
+ super().__init__()
+
+ def run(self) -> None:
+ contact_info_lists = micro_msg_db.get_contact()
+ for contact_info_list in contact_info_lists:
+ # UserName, Alias,Type,Remark,NickName,PYInitial,RemarkPYInitial,ContactHeadImgUrl.smallHeadImgUrl,ContactHeadImgUrl,bigHeadImgUrl
+ 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 = ContactPC(contact_info)
+ contact.smallHeadImgBLOG = misc_db.get_avatar_buffer(contact.wxid)
+ contact.set_avatar(contact.smallHeadImgBLOG)
+ self.showSingal.emit(contact)
+ # pprint(contact.__dict__)
+ self.load_finish_signal.emit(True)
+
+
+class ShowThread(QThread):
+ showSingal = pyqtSignal(ContactPC)
+ load_finish_signal = pyqtSignal(bool)
+
+ # heightSingal = pyqtSignal(int)
+ def __init__(self):
+ super().__init__()
+
+ def run(self) -> None:
+ QThread.sleep(1)
+ self.load_finish_signal.emit(True)
diff --git a/app/ui/contact/__init__.py b/app/ui/contact/__init__.py
new file mode 100644
index 00000000..a83ae0a8
--- /dev/null
+++ b/app/ui/contact/__init__.py
@@ -0,0 +1 @@
+from .contact_window import ContactWindow
diff --git a/app/ui/contact/contactInfo.py b/app/ui/contact/contactInfo.py
new file mode 100644
index 00000000..ba2fae53
--- /dev/null
+++ b/app/ui/contact/contactInfo.py
@@ -0,0 +1,162 @@
+from PyQt5.QtCore import pyqtSignal, QUrl, QThread
+from PyQt5.QtGui import QDesktopServices
+from PyQt5.QtWidgets import QWidget, QMenu, QAction, QToolButton, QMessageBox, QDialog
+
+from app.DataBase.output_pc import Output
+from app.ui.Icon import Icon
+from .contactInfoUi import Ui_Form
+from .userinfo import userinfo
+from ...person import ContactPC
+from .export_dialog import ExportDialog
+
+
+class ContactInfo(QWidget, Ui_Form):
+ exitSignal = pyqtSignal()
+ urlSignal = pyqtSignal(QUrl)
+
+ # username = ''
+ def __init__(self, contact, parent=None):
+ super(ContactInfo, self).__init__(parent)
+ self.setupUi(self)
+ self.contact: ContactPC = contact
+ self.view_userinfo = userinfo.UserinfoController(self.contact)
+ self.btn_back.clicked.connect(self.back)
+ self.init_ui()
+
+ def init_ui(self):
+ self.btn_back.setIcon(Icon.Back)
+ self.btn_report.setIcon(Icon.Annual_Report_Icon)
+ self.btn_analysis.setIcon(Icon.Analysis_Icon)
+ self.btn_emotion.setIcon(Icon.Emotion_Icon)
+ self.btn_report.clicked.connect(self.annual_report)
+ self.btn_analysis.clicked.connect(self.analysis)
+ self.btn_emotion.clicked.connect(self.emotionale_Analysis)
+ self.label_remark.setText(self.contact.remark)
+ self.stackedWidget.addWidget(self.view_userinfo)
+ self.stackedWidget.setCurrentWidget(self.view_userinfo)
+ menu = QMenu(self)
+ self.toDocxAct = QAction(Icon.ToDocx, '导出Docx', self)
+ self.toCSVAct = QAction(Icon.ToCSV, '导出CSV', self)
+ self.toHtmlAct = QAction(Icon.ToHTML, '导出HTML', self)
+ self.toTxtAct = QAction(Icon.ToTXT, '导出TXT', self)
+ self.toolButton_output.setPopupMode(QToolButton.MenuButtonPopup)
+ self.toolButton_output.clicked.connect(self.toolButton_show)
+ menu.addAction(self.toDocxAct)
+ menu.addAction(self.toCSVAct)
+ menu.addAction(self.toHtmlAct)
+ menu.addAction(self.toTxtAct)
+ self.toolButton_output.setMenu(menu)
+ self.toolButton_output.setIcon(Icon.Output)
+ # self.toolButton_output.addSeparator()
+ self.toHtmlAct.triggered.connect(self.output)
+ self.toDocxAct.triggered.connect(self.output)
+ self.toCSVAct.triggered.connect(self.output)
+ self.toTxtAct.triggered.connect(self.output)
+
+ def toolButton_show(self):
+ self.toolButton_output.showMenu()
+
+ def analysis(self):
+ QMessageBox.warning(self,
+ "别急别急",
+ "马上就实现该功能"
+ )
+ return
+ self.stackedWidget.setCurrentWidget(self.view_analysis)
+ if 'room' in self.contact.wxid:
+ QMessageBox.warning(
+ self, '警告',
+ '暂不支持群组'
+ )
+ return
+ self.view_analysis.start()
+
+ def annual_report(self):
+ # QMessageBox.warning(
+ # self,
+ # "提示",
+ # "敬请期待"
+ # )
+ # return
+ self.contact.save_avatar()
+ self.report_thread = ReportThread(self.contact)
+ self.report_thread.okSignal.connect(lambda x: QDesktopServices.openUrl(QUrl("http://127.0.0.1:21314")))
+ self.report_thread.start()
+ QDesktopServices.openUrl(QUrl("http://127.0.0.1:21314"))
+
+ def emotionale_Analysis(self):
+ QMessageBox.warning(self,
+ "别急别急",
+ "马上就实现该功能"
+ )
+ return
+ self.stackedWidget.setCurrentWidget(self.view_emotion)
+ if 'room' in self.contact.wxid:
+ QMessageBox.warning(
+ self, '警告',
+ '暂不支持群组'
+ )
+ return
+ self.view_emotion.start()
+
+ def back(self):
+ """
+ 将userinfo界面设置为可见,其他界面设置为不可见
+ """
+ self.stackedWidget.setCurrentWidget(self.view_userinfo)
+
+ def output(self):
+ """
+ 导出聊天记录
+ :return:
+ """
+ self.stackedWidget.setCurrentWidget(self.view_userinfo)
+ if self.sender() == self.toDocxAct:
+ print('功能暂未实现')
+ QMessageBox.warning(self,
+ "别急别急",
+ "马上就实现该功能"
+ )
+ return
+ self.outputThread = Output(self.Me, self.contact.wxid)
+ elif self.sender() == self.toCSVAct:
+ # self.outputThread = Output(self.contact, type_=Output.CSV)
+ dialog = ExportDialog(self.contact,title='选择导出的消息类型', file_type='csv', parent=self)
+ result = dialog.exec_() # 使用exec_()获取用户的操作结果
+ elif self.sender() == self.toHtmlAct:
+ dialog = ExportDialog(self.contact,title='选择导出的消息类型', file_type='html', parent=self)
+ result = dialog.exec_() # 使用exec_()获取用户的操作结果
+ elif self.sender() == self.toTxtAct:
+ dialog = ExportDialog(self.contact, title='选择导出的消息类型', file_type='txt', parent=self)
+ result = dialog.exec_() # 使用exec_()获取用户的操作结果
+
+ def hide_progress_bar(self, int):
+ reply = QMessageBox(self)
+ reply.setIcon(QMessageBox.Information)
+ reply.setWindowTitle('OK')
+ reply.setText(f"导出聊天记录成功\n在./data/目录下(跟exe文件在一起)")
+ reply.addButton("确认", QMessageBox.AcceptRole)
+ reply.addButton("取消", QMessageBox.RejectRole)
+ api = reply.exec_()
+ self.view_userinfo.progressBar.setVisible(False)
+
+ def output_progress(self, value):
+ self.view_userinfo.progressBar.setProperty('value', value)
+
+ def set_progressBar_range(self, value):
+ self.view_userinfo.progressBar.setVisible(True)
+ self.view_userinfo.progressBar.setRange(0, value)
+
+
+class ReportThread(QThread):
+ okSignal = pyqtSignal(bool)
+
+ def __init__(self, contact):
+ super().__init__()
+ self.contact = contact
+
+ def run(self):
+ from app.web_ui import web
+ web.contact = self.contact
+ web.run(port='21314')
+ self.okSignal.emit(True)
diff --git a/app/ui/contact/contactInfoUi.py b/app/ui/contact/contactInfoUi.py
new file mode 100644
index 00000000..cba0b773
--- /dev/null
+++ b/app/ui/contact/contactInfoUi.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'contactInfoUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ Form.setObjectName("Form")
+ Form.resize(494, 748)
+ self.verticalLayout = QtWidgets.QVBoxLayout(Form)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_3.setSpacing(0)
+ self.horizontalLayout_3.setObjectName("horizontalLayout_3")
+ self.label_remark = QtWidgets.QLabel(Form)
+ self.label_remark.setMaximumSize(QtCore.QSize(120, 100))
+ font = QtGui.QFont()
+ font.setPointSize(12)
+ self.label_remark.setFont(font)
+ self.label_remark.setText("")
+ self.label_remark.setObjectName("label_remark")
+ self.horizontalLayout_3.addWidget(self.label_remark)
+ self.btn_analysis = QtWidgets.QPushButton(Form)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.btn_analysis.sizePolicy().hasHeightForWidth())
+ self.btn_analysis.setSizePolicy(sizePolicy)
+ self.btn_analysis.setStyleSheet("")
+ self.btn_analysis.setFlat(False)
+ self.btn_analysis.setObjectName("btn_analysis")
+ self.horizontalLayout_3.addWidget(self.btn_analysis)
+ self.btn_emotion = QtWidgets.QPushButton(Form)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.btn_emotion.sizePolicy().hasHeightForWidth())
+ self.btn_emotion.setSizePolicy(sizePolicy)
+ self.btn_emotion.setFlat(False)
+ self.btn_emotion.setObjectName("btn_emotion")
+ self.horizontalLayout_3.addWidget(self.btn_emotion)
+ self.btn_report = QtWidgets.QPushButton(Form)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.btn_report.sizePolicy().hasHeightForWidth())
+ self.btn_report.setSizePolicy(sizePolicy)
+ self.btn_report.setFlat(False)
+ self.btn_report.setObjectName("btn_report")
+ self.horizontalLayout_3.addWidget(self.btn_report)
+ self.btn_back = QtWidgets.QPushButton(Form)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.btn_back.sizePolicy().hasHeightForWidth())
+ self.btn_back.setSizePolicy(sizePolicy)
+ self.btn_back.setFlat(False)
+ self.btn_back.setObjectName("btn_back")
+ self.horizontalLayout_3.addWidget(self.btn_back)
+ self.toolButton_output = QtWidgets.QToolButton(Form)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.toolButton_output.sizePolicy().hasHeightForWidth())
+ self.toolButton_output.setSizePolicy(sizePolicy)
+ icon = QtGui.QIcon()
+ icon.addPixmap(QtGui.QPixmap("../../data/icons/output.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+ self.toolButton_output.setIcon(icon)
+ self.toolButton_output.setCheckable(False)
+ self.toolButton_output.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
+ self.toolButton_output.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
+ self.toolButton_output.setAutoRaise(True)
+ self.toolButton_output.setArrowType(QtCore.Qt.NoArrow)
+ self.toolButton_output.setObjectName("toolButton_output")
+ self.horizontalLayout_3.addWidget(self.toolButton_output)
+ self.horizontalLayout_3.setStretch(0, 1)
+ self.verticalLayout.addLayout(self.horizontalLayout_3)
+ self.stackedWidget = QtWidgets.QStackedWidget(Form)
+ self.stackedWidget.setObjectName("stackedWidget")
+ self.page_3 = QtWidgets.QWidget()
+ self.page_3.setObjectName("page_3")
+ self.stackedWidget.addWidget(self.page_3)
+ self.page_4 = QtWidgets.QWidget()
+ self.page_4.setObjectName("page_4")
+ self.stackedWidget.addWidget(self.page_4)
+ self.verticalLayout.addWidget(self.stackedWidget)
+
+ self.retranslateUi(Form)
+ self.stackedWidget.setCurrentIndex(1)
+ QtCore.QMetaObject.connectSlotsByName(Form)
+
+ def retranslateUi(self, Form):
+ _translate = QtCore.QCoreApplication.translate
+ Form.setWindowTitle(_translate("Form", "Form"))
+ self.btn_analysis.setText(_translate("Form", "统计信息"))
+ self.btn_emotion.setText(_translate("Form", "情感分析"))
+ self.btn_report.setText(_translate("Form", "年度报告"))
+ self.btn_back.setText(_translate("Form", "退出"))
+ self.toolButton_output.setText(_translate("Form", "导出聊天记录"))
diff --git a/app/ui/contact/contactUi.py b/app/ui/contact/contactUi.py
new file mode 100644
index 00000000..ff13d8cd
--- /dev/null
+++ b/app/ui/contact/contactUi.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'contactUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtWidgets
+
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ Form.setObjectName("Form")
+ Form.resize(840, 752)
+ Form.setStyleSheet("background: rgb(240, 240, 240);")
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout(Form)
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout()
+ self.verticalLayout_2.setSpacing(6)
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.verticalLayout = QtWidgets.QVBoxLayout()
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.label = QtWidgets.QLabel(Form)
+ self.label.setText("")
+ self.label.setObjectName("label")
+ self.horizontalLayout.addWidget(self.label)
+ self.lineEdit = QtWidgets.QLineEdit(Form)
+ self.lineEdit.setMinimumSize(QtCore.QSize(200, 30))
+ self.lineEdit.setMaximumSize(QtCore.QSize(200, 16777215))
+ self.lineEdit.setStyleSheet("background:transparent;\n"
+ "border-radius:5px;\n"
+ "border-top: 0px solid #b2e281;\n"
+ "border-bottom: 0px solid #b2e281;\n"
+ "border-right: 0px solid #b2e281;\n"
+ "border-left: 0px solid #b2e281;\n"
+ "border-style:outset;\n"
+ "background-color:rgb(226,226,226);\n"
+ " ")
+ self.lineEdit.setCursorMoveStyle(QtCore.Qt.VisualMoveStyle)
+ self.lineEdit.setObjectName("lineEdit")
+ self.horizontalLayout.addWidget(self.lineEdit)
+ self.label_2 = QtWidgets.QLabel(Form)
+ self.label_2.setMinimumSize(QtCore.QSize(30, 0))
+ self.label_2.setMaximumSize(QtCore.QSize(30, 16777215))
+ self.label_2.setText("")
+ self.label_2.setObjectName("label_2")
+ self.horizontalLayout.addWidget(self.label_2)
+ self.verticalLayout.addLayout(self.horizontalLayout)
+ self.verticalLayout_2.addLayout(self.verticalLayout)
+ self.listWidget = QtWidgets.QListWidget(Form)
+ self.listWidget.setMinimumSize(QtCore.QSize(250, 0))
+ self.listWidget.setMaximumSize(QtCore.QSize(250, 16777215))
+ self.listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.listWidget.setObjectName("listWidget")
+ self.verticalLayout_2.addWidget(self.listWidget)
+ self.verticalLayout_2.setStretch(1, 1)
+ self.horizontalLayout_2.addLayout(self.verticalLayout_2)
+ self.stackedWidget = QtWidgets.QStackedWidget(Form)
+ self.stackedWidget.setObjectName("stackedWidget")
+ self.horizontalLayout_2.addWidget(self.stackedWidget)
+ self.horizontalLayout_2.setStretch(1, 1)
+
+ self.retranslateUi(Form)
+ self.stackedWidget.setCurrentIndex(-1)
+ QtCore.QMetaObject.connectSlotsByName(Form)
+
+ def retranslateUi(self, Form):
+ _translate = QtCore.QCoreApplication.translate
+ Form.setWindowTitle(_translate("Form", "Form"))
diff --git a/app/ui/contact/contact_window.py b/app/ui/contact/contact_window.py
new file mode 100644
index 00000000..9f3e71b0
--- /dev/null
+++ b/app/ui/contact/contact_window.py
@@ -0,0 +1,155 @@
+from PyQt5.QtCore import QThread, pyqtSignal
+from PyQt5.QtWidgets import QWidget, QMessageBox, QAction, QLineEdit
+
+from app.DataBase import micro_msg_db, misc_db
+from app.components import ContactQListWidgetItem
+from app.person import ContactPC
+from app.ui.Icon import Icon
+from .contactInfo import ContactInfo
+from .contactUi import Ui_Form
+from ...util import search
+
+# 美化样式表
+Stylesheet = """
+QPushButton{
+ background-color: transparent;
+}
+QPushButton:hover {
+ background-color: lightgray;
+}
+/*去掉item虚线边框*/
+QListWidget, QListView, QTreeWidget, QTreeView {
+ outline: 0px;
+ border:none;
+ background-color:rgb(240,240,240)
+}
+/*设置左侧选项的最小最大宽度,文字颜色和背景颜色*/
+QListWidget {
+ min-width: 250px;
+ max-width: 250px;
+ min-height: 80px;
+ max-height: 1200px;
+ color: black;
+ border:none;
+}
+QListWidget::item{
+ height:60px;
+ width:250px;
+}
+/*被选中时的背景颜色和左边框颜色*/
+QListWidget::item:selected {
+ background: rgb(204, 204, 204);
+ border-bottom: 2px solid rgb(9, 187, 7);
+ border-left:none;
+ color: black;
+ font-weight: bold;
+}
+/*鼠标悬停颜色*/
+HistoryPanel::item:hover {
+ background: rgb(52, 52, 52);
+}
+"""
+
+
+class ContactWindow(QWidget, Ui_Form):
+ load_finish_signal = pyqtSignal(bool)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.show_thread = None
+ self.setupUi(self)
+ self.ok_flag = False
+ self.setStyleSheet(Stylesheet)
+ self.init_ui()
+ self.contacts = [[], []]
+ self.show_contacts()
+
+ def init_ui(self):
+ search_action = QAction(self.lineEdit)
+ search_action.setIcon(Icon.Search_Icon)
+ self.lineEdit.addAction(search_action, QLineEdit.LeadingPosition)
+ self.lineEdit.returnPressed.connect(self.search_contact)
+ self.listWidget.clear()
+ self.listWidget.currentRowChanged.connect(self.setCurrentIndex)
+ self.listWidget.setCurrentRow(0)
+ self.stackedWidget.setCurrentIndex(0)
+
+ def show_contacts(self):
+ # return
+ if self.ok_flag:
+ return
+ micro_msg_db.init_database()
+ if not micro_msg_db.open_flag:
+ QMessageBox.critical(self, "错误", "数据库不存在\n请先解密数据库")
+ self.show_thread = ShowThread()
+ self.show_thread.showSingal.connect(self.show_contact)
+ self.show_thread.load_finish_signal.connect(self.load_finish_signal)
+ self.show_thread.start()
+ return
+
+ self.show_thread = ShowContactThread()
+ self.show_thread.showSingal.connect(self.show_contact)
+ self.show_thread.load_finish_signal.connect(self.load_finish_signal)
+ self.show_thread.start()
+ self.ok_flag = True
+
+ def search_contact(self):
+ keyword = self.lineEdit.text()
+ if keyword:
+ index = search.search_by_content(keyword, self.contacts)
+ self.listWidget.setCurrentRow(index)
+ self.stackedWidget.setCurrentIndex(index)
+
+ def show_contact(self, contact: ContactPC):
+ self.contacts[0].append(contact.remark)
+ self.contacts[1].append(contact.nickName)
+ contact_item = ContactQListWidgetItem(contact.remark, contact.smallHeadImgUrl, contact.smallHeadImgBLOG)
+ self.listWidget.addItem(contact_item)
+ self.listWidget.setItemWidget(contact_item, contact_item.widget)
+ contact_info_window = ContactInfo(contact)
+ self.stackedWidget.addWidget(contact_info_window)
+
+ def setCurrentIndex(self, row):
+ # print(row)
+ self.stackedWidget.setCurrentIndex(row)
+
+
+class ShowContactThread(QThread):
+ showSingal = pyqtSignal(ContactPC)
+ load_finish_signal = pyqtSignal(bool)
+
+ # heightSingal = pyqtSignal(int)
+ def __init__(self):
+ super().__init__()
+
+ def run(self) -> None:
+ contact_info_lists = micro_msg_db.get_contact()
+ for contact_info_list in contact_info_lists:
+ # UserName, Alias,Type,Remark,NickName,PYInitial,RemarkPYInitial,ContactHeadImgUrl.smallHeadImgUrl,ContactHeadImgUrl,bigHeadImgUrl
+ 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 = ContactPC(contact_info)
+ contact.smallHeadImgBLOG = misc_db.get_avatar_buffer(contact.wxid)
+ contact.set_avatar(contact.smallHeadImgBLOG)
+ self.showSingal.emit(contact)
+ # pprint(contact.__dict__)
+ self.load_finish_signal.emit(True)
+
+
+class ShowThread(QThread):
+ showSingal = pyqtSignal(ContactPC)
+ load_finish_signal = pyqtSignal(bool)
+
+ # heightSingal = pyqtSignal(int)
+ def __init__(self):
+ super().__init__()
+
+ def run(self) -> None:
+ QThread.sleep(1)
+ self.load_finish_signal.emit(True)
diff --git a/app/ui/contact/export_dialog.py b/app/ui/contact/export_dialog.py
new file mode 100644
index 00000000..7786fc6e
--- /dev/null
+++ b/app/ui/contact/export_dialog.py
@@ -0,0 +1,105 @@
+from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QDialog, QVBoxLayout, QCheckBox, QHBoxLayout, \
+ QProgressBar, QLabel, QMessageBox
+
+from app.DataBase.output_pc import Output
+
+types = {
+ '文本': 1,
+ '图片': 3,
+ '语音': 34,
+ '视频': 43,
+ '表情包': 47,
+ '拍一拍等系统消息': 10000
+}
+Stylesheet = """
+QPushButton{
+ background-color: #ffffff;
+}
+QPushButton:hover {
+ background-color: lightgray;
+}
+"""
+
+class ExportDialog(QDialog):
+ def __init__(self, contact=None, title="选择导出的类型", file_type="csv", parent=None):
+ super(ExportDialog, self).__init__(parent)
+ self.setStyleSheet(Stylesheet)
+ self.contact = contact
+ if file_type == 'html':
+ self.export_type = Output.HTML
+ self.export_choices = {"文本": True, "图片": True, "语音": True, "视频": True, "表情包": True,
+ '拍一拍等系统消息': True} # 定义导出的数据类型,默认全部选择
+ elif file_type == 'csv':
+ self.export_type = Output.CSV
+ self.export_choices = {"文本": True, "图片": True, "视频": True, "表情包": True} # 定义导出的数据类型,默认全部选择
+ elif file_type == 'txt':
+ self.export_type = Output.TXT
+ self.export_choices = {"文本": True, "图片": True, "语音": True, "视频": True, "表情包": True} # 定义导出的数据类型,默认全部选择
+ else:
+ self.export_choices = {"文本": True, "图片": True, "视频": True, "表情包": True} # 定义导出的数据类型,默认全部选择
+ self.setWindowTitle(title)
+ layout = QVBoxLayout(self)
+ self.resize(400, 300)
+ self.worker = None # 导出线程
+ self.progress_bar = QProgressBar(self)
+ self.progress_label = QLabel(self)
+ for export_type, default_state in self.export_choices.items():
+ checkbox = QCheckBox(export_type)
+ checkbox.setChecked(default_state)
+ layout.addWidget(checkbox)
+ layout.addWidget(self.progress_bar)
+ layout.addWidget(self.progress_label)
+ hlayout = QHBoxLayout(self)
+ self.export_button = QPushButton("导出")
+ self.export_button.clicked.connect(self.export_data)
+ hlayout.addWidget(self.export_button)
+
+ self.cancel_button = QPushButton("取消")
+ self.cancel_button.clicked.connect(self.reject) # 使用reject关闭对话框
+ hlayout.addWidget(self.cancel_button)
+ layout.addLayout(hlayout)
+ self.setLayout(layout)
+
+ def export_data(self):
+ self.export_button.setEnabled(False)
+ self.cancel_button.setEnabled(False)
+ # 在这里获取用户选择的导出数据类型
+ selected_types = {types[export_type]: checkbox.isChecked() for export_type, checkbox in
+ zip(self.export_choices.keys(), self.findChildren(QCheckBox))}
+
+ # 在这里根据用户选择的数据类型执行导出操作
+ print("选择的数据类型:", selected_types)
+ self.worker = Output(self.contact, type_=self.export_type, message_types=selected_types)
+ self.worker.progressSignal.connect(self.update_progress)
+ self.worker.okSignal.connect(self.export_finished)
+ self.worker.start()
+ # self.accept() # 使用accept关闭对话框
+
+ def export_finished(self):
+ self.export_button.setEnabled(True)
+ self.cancel_button.setEnabled(True)
+ reply = QMessageBox(self)
+ reply.setIcon(QMessageBox.Information)
+ reply.setWindowTitle('OK')
+ reply.setText(f"导出聊天记录成功\n在./data/目录下(跟exe文件在一起)")
+ reply.addButton("确认", QMessageBox.AcceptRole)
+ reply.addButton("取消", QMessageBox.RejectRole)
+ api = reply.exec_()
+ self.accept()
+
+ def update_progress(self, progress_percentage):
+ self.progress_bar.setValue(progress_percentage)
+ self.progress_label.setText(f"导出进度: {progress_percentage}%")
+
+
+if __name__ == '__main__':
+ import sys
+
+ app = QApplication(sys.argv)
+ dialog = ExportDialog()
+ result = dialog.exec_() # 使用exec_()获取用户的操作结果
+ if result == QDialog.Accepted:
+ print("用户点击了导出按钮")
+ else:
+ print("用户点击了取消按钮")
+ sys.exit(app.exec_())
diff --git a/app/ui/contact/userinfo/__init__.py b/app/ui/contact/userinfo/__init__.py
new file mode 100644
index 00000000..6d32454d
--- /dev/null
+++ b/app/ui/contact/userinfo/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@File : __init__.py.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/24 10:34
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
diff --git a/app/ui/contact/userinfo/userinfo.py b/app/ui/contact/userinfo/userinfo.py
new file mode 100644
index 00000000..5a6ac0bf
--- /dev/null
+++ b/app/ui/contact/userinfo/userinfo.py
@@ -0,0 +1,15 @@
+from PyQt5.QtWidgets import *
+
+from .userinfoUi import Ui_Frame
+
+
+class UserinfoController(QWidget, Ui_Frame):
+ def __init__(self, contact, parent=None):
+ super().__init__(parent)
+ self.setupUi(self)
+ self.l_remark.setText(contact.remark)
+ self.l_avatar.setPixmap(contact.avatar)
+ self.l_nickname.setText(f'昵称:{contact.nickName}')
+ self.l_username.setText(f'微信号:{contact.alias}')
+ self.lineEdit.setText(contact.remark)
+ self.progressBar.setVisible(False)
diff --git a/app/ui/contact/userinfo/userinfoUi.py b/app/ui/contact/userinfo/userinfoUi.py
new file mode 100644
index 00000000..8494e5d3
--- /dev/null
+++ b/app/ui/contact/userinfo/userinfoUi.py
@@ -0,0 +1,123 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'userinfoUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Frame(object):
+ def setupUi(self, Frame):
+ Frame.setObjectName("Frame")
+ Frame.resize(624, 720)
+ Frame.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor))
+ Frame.setMouseTracking(True)
+ Frame.setTabletTracking(True)
+ self.horizontalLayout_3 = QtWidgets.QHBoxLayout(Frame)
+ self.horizontalLayout_3.setObjectName("horizontalLayout_3")
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_2.addItem(spacerItem)
+ self.verticalLayout = QtWidgets.QVBoxLayout()
+ self.verticalLayout.setObjectName("verticalLayout")
+ spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout.addItem(spacerItem1)
+ self.gridLayout = QtWidgets.QGridLayout()
+ self.gridLayout.setHorizontalSpacing(6)
+ self.gridLayout.setObjectName("gridLayout")
+ self.l_avatar = QtWidgets.QLabel(Frame)
+ self.l_avatar.setMinimumSize(QtCore.QSize(80, 80))
+ self.l_avatar.setMaximumSize(QtCore.QSize(80, 80))
+ self.l_avatar.setText("")
+ self.l_avatar.setPixmap(QtGui.QPixmap("../../../a_img/be0fa6c0c4707fb5f7b37b660de826d3.jpg"))
+ self.l_avatar.setScaledContents(True)
+ self.l_avatar.setObjectName("l_avatar")
+ self.gridLayout.addWidget(self.l_avatar, 0, 0, 3, 1)
+ self.l_remark = QtWidgets.QLabel(Frame)
+ self.l_remark.setMinimumSize(QtCore.QSize(0, 30))
+ self.l_remark.setMaximumSize(QtCore.QSize(16777215, 30))
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.l_remark.setFont(font)
+ self.l_remark.setObjectName("l_remark")
+ self.gridLayout.addWidget(self.l_remark, 0, 1, 1, 1)
+ self.l_nickname = QtWidgets.QLabel(Frame)
+ self.l_nickname.setMinimumSize(QtCore.QSize(0, 30))
+ self.l_nickname.setMaximumSize(QtCore.QSize(16777215, 30))
+ self.l_nickname.setObjectName("l_nickname")
+ self.gridLayout.addWidget(self.l_nickname, 1, 1, 1, 1)
+ self.l_username = QtWidgets.QLabel(Frame)
+ self.l_username.setMinimumSize(QtCore.QSize(0, 20))
+ self.l_username.setMaximumSize(QtCore.QSize(16777215, 20))
+ self.l_username.setObjectName("l_username")
+ self.gridLayout.addWidget(self.l_username, 2, 1, 1, 1)
+ self.gridLayout.setRowStretch(0, 1)
+ self.gridLayout.setRowStretch(1, 1)
+ self.gridLayout.setRowStretch(2, 1)
+ self.verticalLayout.addLayout(self.gridLayout)
+ spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout.addItem(spacerItem2)
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.label = QtWidgets.QLabel(Frame)
+ self.label.setMinimumSize(QtCore.QSize(80, 0))
+ self.label.setMaximumSize(QtCore.QSize(80, 16777215))
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.label.setFont(font)
+ self.label.setObjectName("label")
+ self.horizontalLayout.addWidget(self.label)
+ self.lineEdit = QtWidgets.QLineEdit(Frame)
+ self.lineEdit.setMinimumSize(QtCore.QSize(0, 25))
+ self.lineEdit.setMaximumSize(QtCore.QSize(16777215, 25))
+ font = QtGui.QFont()
+ font.setPointSize(15)
+ self.lineEdit.setFont(font)
+ self.lineEdit.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
+ self.lineEdit.setAutoFillBackground(False)
+ self.lineEdit.setStyleSheet("\n"
+" background:transparent;border-width:0;border-style:outset\n"
+" ")
+ self.lineEdit.setObjectName("lineEdit")
+ self.horizontalLayout.addWidget(self.lineEdit)
+ self.verticalLayout.addLayout(self.horizontalLayout)
+ spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout.addItem(spacerItem3)
+ self.progressBar = QtWidgets.QProgressBar(Frame)
+ self.progressBar.setProperty("value", 24)
+ self.progressBar.setObjectName("progressBar")
+ self.verticalLayout.addWidget(self.progressBar)
+ spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout.addItem(spacerItem4)
+ self.verticalLayout.setStretch(0, 2)
+ self.verticalLayout.setStretch(1, 3)
+ self.verticalLayout.setStretch(2, 4)
+ self.verticalLayout.setStretch(3, 1)
+ self.verticalLayout.setStretch(4, 4)
+ self.verticalLayout.setStretch(5, 1)
+ self.verticalLayout.setStretch(6, 2)
+ self.horizontalLayout_2.addLayout(self.verticalLayout)
+ spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_2.addItem(spacerItem5)
+ self.horizontalLayout_2.setStretch(0, 1)
+ self.horizontalLayout_2.setStretch(1, 2)
+ self.horizontalLayout_2.setStretch(2, 1)
+ self.horizontalLayout_3.addLayout(self.horizontalLayout_2)
+
+ self.retranslateUi(Frame)
+ QtCore.QMetaObject.connectSlotsByName(Frame)
+
+ def retranslateUi(self, Frame):
+ _translate = QtCore.QCoreApplication.translate
+ Frame.setWindowTitle(_translate("Frame", "Frame"))
+ self.l_remark.setText(_translate("Frame", "曹雨萱"))
+ self.l_nickname.setText(_translate("Frame", "昵称:997"))
+ self.l_username.setText(_translate("Frame", "账号:TextLabel"))
+ self.label.setText(_translate("Frame", "备注名"))
+ self.lineEdit.setText(_translate("Frame", "曹雨萱"))
diff --git a/app/ui/mainview.py b/app/ui/mainview.py
new file mode 100644
index 00000000..da87490c
--- /dev/null
+++ b/app/ui/mainview.py
@@ -0,0 +1,250 @@
+# -*- coding: utf-8 -*-
+"""
+@File : mainview.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/13 15:07
+@IDE : Pycharm
+@Version : Python3.10
+@comment : 主窗口
+"""
+import json
+import os.path
+
+from PyQt5.QtCore import pyqtSignal, QUrl, Qt, QThread, QSize
+from PyQt5.QtGui import QPixmap, QFont, QDesktopServices
+from PyQt5.QtWidgets import QMainWindow, QLabel, QListWidgetItem, QMessageBox
+
+from app import config
+from app.DataBase import msg_db, misc_db, micro_msg_db, hard_link_db
+from app.ui.Icon import Icon
+from . import mainwindow
+from .chat import ChatWindow
+from .contact import ContactWindow
+from .tool.tool_window import ToolWindow
+from ..DataBase.output_pc import Output
+from ..person import MePC
+
+# 美化样式表
+Stylesheet = """
+
+/*去掉item虚线边框*/
+QListWidget, QListView, QTreeWidget, QTreeView {
+ outline: 0px;
+}
+/*设置左侧选项的最小最大宽度,文字颜色和背景颜色*/
+QListWidget {
+ min-width: 120px;
+ max-width: 120px;
+ color: black;
+ background: white;
+ border:none;
+}
+QListWidget::item{
+ height:60;
+}
+/*被选中时的背景颜色和左边框颜色*/
+QListWidget::item:selected {
+ background: rgb(204, 204, 204);
+ border-left: 4px solid rgb(9, 187, 7);
+ color: black;
+ font-weight: bold;
+}
+/*鼠标悬停颜色*/
+HistoryPanel::item:hover {
+ background: rgb(52, 52, 52);
+}
+"""
+
+
+class MainWinController(QMainWindow, mainwindow.Ui_MainWindow):
+ exitSignal = pyqtSignal(bool)
+ okSignal = pyqtSignal(bool)
+
+ # username = ''
+ def __init__(self, username, parent=None):
+ super(MainWinController, self).__init__(parent)
+ self.outputThread0 = None
+ self.outputThread = None
+ self.setupUi(self)
+ self.setWindowIcon(Icon.MainWindow_Icon)
+ self.setStyleSheet(Stylesheet)
+ self.listWidget.clear()
+ self.resize(QSize(800, 600))
+ self.action_desc.triggered.connect(self.about)
+ self.load_flag = False
+ self.load_data()
+ self.load_num = 0
+ 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'))
+
+ def load_data(self, flag=True):
+ if os.path.exists('./app/data/info.json'):
+ with open('./app/data/info.json', 'r', encoding='utf-8') as f:
+ dic = json.loads(f.read())
+ wxid = dic.get('wxid')
+ if wxid:
+ me = MePC()
+ me.wxid = dic.get('wxid')
+ me.name = dic.get('name')
+ me.mobile = dic.get('mobile')
+ me.wx_dir = dic.get('wx_dir')
+ self.set_my_info(wxid)
+ self.load_flag = True
+ else:
+ QMessageBox.information(
+ self,
+ '温馨提示',
+ '点击 工具->获取信息 重启后可以显示本人头像哦'
+ )
+
+ def init_ui(self):
+ 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_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(
+ lambda: QDesktopServices.openUrl(QUrl("https://blog.lc044.love/post/5")))
+ self.action_help_decrypt.triggered.connect(
+ lambda: QDesktopServices.openUrl(QUrl("https://blog.lc044.love/post/4")))
+ self.listWidget.setVisible(False)
+ self.stackedWidget.setVisible(False)
+ self.listWidget.currentRowChanged.connect(self.setCurrentIndex)
+ tool_item = QListWidgetItem(Icon.Tool_Icon, '工具', self.listWidget)
+ chat_item = QListWidgetItem(Icon.Chat_Icon, '聊天', self.listWidget)
+ contact_item = QListWidgetItem(Icon.Contact_Icon, '好友', self.listWidget)
+ myinfo_item = QListWidgetItem(Icon.Home_Icon, '我的', self.listWidget)
+ tool_window = ToolWindow()
+ tool_window.get_info_signal.connect(self.set_my_info)
+ tool_window.decrypt_success_signal.connect(self.decrypt_success)
+ tool_window.load_finish_signal.connect(self.loading)
+ self.stackedWidget.addWidget(tool_window)
+ self.chat_window = ChatWindow()
+ # chat_window = QWidget()
+ self.stackedWidget.addWidget(self.chat_window)
+ self.contact_window = ContactWindow()
+ self.stackedWidget.addWidget(self.contact_window)
+ label = QLabel('该功能暂不支持哦')
+ label.setFont(QFont("微软雅黑", 50))
+ label.setAlignment(Qt.AlignCenter)
+ self.stackedWidget.addWidget(label)
+ tool_window.load_finish_signal.connect(self.loading)
+ self.statusbar.showMessage('聊天窗口上划到顶部会加载新的聊天记录\n一次不行那就多来几次')
+ self.contact_window.load_finish_signal.connect(self.loading)
+ self.chat_window.load_finish_signal.connect(self.loading)
+
+ def setCurrentIndex(self, row):
+ self.stackedWidget.setCurrentIndex(row)
+ if row == 2:
+ self.stackedWidget.currentWidget().show_contacts()
+ if row == 1:
+ self.stackedWidget.currentWidget().show_chats()
+
+ def setWindow(self, window):
+ try:
+ window.load_finish_signal.connect(self.loading)
+ except:
+ pass
+ self.stackedWidget.addWidget(window)
+
+ def set_my_info(self, wxid):
+ self.avatar = QPixmap()
+ try:
+ img_bytes = misc_db.get_avatar_buffer(wxid)
+ except AttributeError:
+ return
+ if not img_bytes:
+ return
+ if img_bytes[:4] == b'\x89PNG':
+ self.avatar.loadFromData(img_bytes, format='PNG')
+ else:
+ self.avatar.loadFromData(img_bytes, format='jfif')
+ self.avatar.scaled(60, 60)
+ me = MePC()
+ me.set_avatar(img_bytes)
+ self.myavatar.setScaledContents(True)
+ self.myavatar.setPixmap(self.avatar)
+
+ def stop_loading(self, a0):
+ self.label.setVisible(False)
+
+ def loading(self, a0):
+ self.load_num += 1
+ # self.label.setVisible(False)
+ print('加载一个了')
+ if self.load_num == 1:
+ print('ok了')
+ self.label.clear()
+ self.label.hide()
+ self.okSignal.emit(True)
+ self.listWidget.setVisible(True)
+ self.stackedWidget.setVisible(True)
+ if self.load_flag:
+ self.listWidget.setCurrentRow(1)
+ self.stackedWidget.setCurrentIndex(1)
+ else:
+ self.listWidget.setCurrentRow(0)
+ self.stackedWidget.setCurrentIndex(0)
+
+ def output(self):
+ if self.sender() == self.action_output_CSV:
+ self.outputThread = Output(None, type_=Output.CSV_ALL)
+ self.outputThread.okSignal.connect(
+ lambda x: self.message('聊天记录导出成功'))
+ self.outputThread.start()
+ elif self.sender() == self.action_output_contacts:
+ self.outputThread = Output(None, type_=Output.CONTACT_CSV)
+ self.outputThread.okSignal.connect(
+ lambda x: self.message('联系人导出成功'))
+ self.outputThread.start()
+
+ def message(self, msg):
+ QMessageBox.about(self, "提醒", msg)
+
+ def about(self):
+ """
+ 关于
+ """
+ QMessageBox.about(self, "关于",
+ f'''版本:{config.version}
QQ交流群:{config.contact}
地址:https://github.com/LC044/WeChatMsg
新特性:
{''.join(['' + i for i in config.description])}
+ '''
+ )
+
+ def decrypt_success(self):
+ QMessageBox.about(self, "解密成功", "请重新启动")
+ self.close()
+
+ def close(self) -> bool:
+ super().close()
+ misc_db.close()
+ msg_db.close()
+ micro_msg_db.close()
+ hard_link_db.close()
+ self.contact_window.close()
+ self.exitSignal.emit(True)
+
+
+class LoadWindowThread(QThread):
+ okSignal = pyqtSignal(bool)
+
+ def __init__(self):
+ super().__init__()
+ self.num = 0
+
+ def loading(self):
+ self.num += 1
+ print('加载一个了')
+ if self.num == 2:
+ self.okSignal.emit(True)
+
+ def run(self):
+ self.chat_window = ChatWindow()
+ self.contact_window = ContactWindow()
+ self.contact_window.load_finish_signal.connect(self.loading)
+ self.chat_window.load_finish_signal.connect(self.loading)
+ print('加载完成')
+ self.okSignal.emit(True)
diff --git a/app/ui/mainwindow.py b/app/ui/mainwindow.py
new file mode 100644
index 00000000..20e242c5
--- /dev/null
+++ b/app/ui/mainwindow.py
@@ -0,0 +1,179 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'mainwindow.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_MainWindow(object):
+ def setupUi(self, MainWindow):
+ MainWindow.setObjectName("MainWindow")
+ MainWindow.resize(1280, 779)
+ MainWindow.setStyleSheet("\n"
+" /*去掉item虚线边框*/\n"
+" QListWidget, QListView, QTreeWidget, QTreeView {\n"
+" outline: 0px;\n"
+" }\n"
+" /*设置左侧选项的最小最大宽度,文字颜色和背景颜色*/\n"
+" QListWidget {\n"
+" min-width: 120px;\n"
+" max-width: 120px;\n"
+" color: black;\n"
+" background: white;\n"
+" border:none;\n"
+" }\n"
+" QListWidget::item{\n"
+" height:80;\n"
+" }\n"
+" /*被选中时的背景颜色和左边框颜色*/\n"
+" QListWidget::item:selected {\n"
+" background: rgb(204, 204, 204);\n"
+" border-left: 4px solid rgb(9, 187, 7);\n"
+" }\n"
+" /*鼠标悬停颜色*/\n"
+" HistoryPanel::item:hover {\n"
+" background: rgb(52, 52, 52);\n"
+" }\n"
+" ")
+ self.centralwidget = QtWidgets.QWidget(MainWindow)
+ self.centralwidget.setObjectName("centralwidget")
+ self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
+ self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
+ self.horizontalLayout.setSpacing(0)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.frame_info = QtWidgets.QFrame(self.centralwidget)
+ self.frame_info.setMinimumSize(QtCore.QSize(80, 500))
+ self.frame_info.setMaximumSize(QtCore.QSize(80, 16777215))
+ self.frame_info.setStyleSheet("background-color:rgb(240,240,240)")
+ self.frame_info.setFrameShape(QtWidgets.QFrame.NoFrame)
+ self.frame_info.setFrameShadow(QtWidgets.QFrame.Plain)
+ self.frame_info.setObjectName("frame_info")
+ self.myavatar = QtWidgets.QLabel(self.frame_info)
+ self.myavatar.setGeometry(QtCore.QRect(10, 40, 60, 60))
+ self.myavatar.setObjectName("myavatar")
+ self.listWidget = QtWidgets.QListWidget(self.frame_info)
+ self.listWidget.setGeometry(QtCore.QRect(0, 230, 120, 331))
+ self.listWidget.setMinimumSize(QtCore.QSize(120, 0))
+ self.listWidget.setMaximumSize(QtCore.QSize(120, 16777215))
+ self.listWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.listWidget.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustIgnored)
+ self.listWidget.setObjectName("listWidget")
+ item = QtWidgets.QListWidgetItem()
+ self.listWidget.addItem(item)
+ item = QtWidgets.QListWidgetItem()
+ self.listWidget.addItem(item)
+ item = QtWidgets.QListWidgetItem()
+ self.listWidget.addItem(item)
+ item = QtWidgets.QListWidgetItem()
+ self.listWidget.addItem(item)
+ item = QtWidgets.QListWidgetItem()
+ self.listWidget.addItem(item)
+ self.horizontalLayout.addWidget(self.frame_info)
+ self.stackedWidget = QtWidgets.QStackedWidget(self.centralwidget)
+ font = QtGui.QFont()
+ font.setFamily("微软雅黑")
+ font.setBold(False)
+ font.setWeight(50)
+ self.stackedWidget.setFont(font)
+ self.stackedWidget.setObjectName("stackedWidget")
+ self.horizontalLayout.addWidget(self.stackedWidget)
+ MainWindow.setCentralWidget(self.centralwidget)
+ self.menubar = QtWidgets.QMenuBar(MainWindow)
+ self.menubar.setGeometry(QtCore.QRect(0, 0, 1280, 23))
+ self.menubar.setObjectName("menubar")
+ self.menu_F = QtWidgets.QMenu(self.menubar)
+ self.menu_F.setObjectName("menu_F")
+ self.menu_data = QtWidgets.QMenu(self.menubar)
+ font = QtGui.QFont()
+ font.setFamily("微软雅黑")
+ self.menu_data.setFont(font)
+ self.menu_data.setObjectName("menu_data")
+ self.menu_output = QtWidgets.QMenu(self.menu_data)
+ self.menu_output.setObjectName("menu_output")
+ self.menu_2 = QtWidgets.QMenu(self.menubar)
+ self.menu_2.setObjectName("menu_2")
+ self.menu_about = QtWidgets.QMenu(self.menubar)
+ self.menu_about.setObjectName("menu_about")
+ self.menu_3 = QtWidgets.QMenu(self.menubar)
+ self.menu_3.setObjectName("menu_3")
+ MainWindow.setMenuBar(self.menubar)
+ self.statusbar = QtWidgets.QStatusBar(MainWindow)
+ self.statusbar.setObjectName("statusbar")
+ MainWindow.setStatusBar(self.statusbar)
+ self.action_3 = QtWidgets.QAction(MainWindow)
+ self.action_3.setObjectName("action_3")
+ self.action_4 = QtWidgets.QAction(MainWindow)
+ self.action_4.setObjectName("action_4")
+ self.action_help_decrypt = QtWidgets.QAction(MainWindow)
+ font = QtGui.QFont()
+ font.setFamily("Microsoft YaHei UI")
+ self.action_help_decrypt.setFont(font)
+ self.action_help_decrypt.setObjectName("action_help_decrypt")
+ self.action_desc = QtWidgets.QAction(MainWindow)
+ self.action_desc.setObjectName("action_desc")
+ self.action_help_chat = QtWidgets.QAction(MainWindow)
+ self.action_help_chat.setObjectName("action_help_chat")
+ self.action_help_contact = QtWidgets.QAction(MainWindow)
+ self.action_help_contact.setObjectName("action_help_contact")
+ self.action_output_CSV = QtWidgets.QAction(MainWindow)
+ self.action_output_CSV.setObjectName("action_output_CSV")
+ self.action_output_contacts = QtWidgets.QAction(MainWindow)
+ self.action_output_contacts.setObjectName("action_output_contacts")
+ self.menu_F.addSeparator()
+ self.menu_F.addSeparator()
+ self.menu_F.addAction(self.action_3)
+ self.menu_F.addAction(self.action_4)
+ self.menu_output.addAction(self.action_output_CSV)
+ self.menu_data.addAction(self.menu_output.menuAction())
+ self.menu_data.addAction(self.action_output_contacts)
+ self.menu_2.addAction(self.action_help_decrypt)
+ self.menu_2.addAction(self.action_help_chat)
+ self.menu_2.addAction(self.action_help_contact)
+ self.menu_about.addAction(self.action_desc)
+ self.menubar.addAction(self.menu_F.menuAction())
+ self.menubar.addAction(self.menu_data.menuAction())
+ self.menubar.addAction(self.menu_2.menuAction())
+ self.menubar.addAction(self.menu_about.menuAction())
+ self.menubar.addAction(self.menu_3.menuAction())
+
+ self.retranslateUi(MainWindow)
+ QtCore.QMetaObject.connectSlotsByName(MainWindow)
+
+ def retranslateUi(self, MainWindow):
+ _translate = QtCore.QCoreApplication.translate
+ MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
+ self.myavatar.setText(_translate("MainWindow", "avatar"))
+ __sortingEnabled = self.listWidget.isSortingEnabled()
+ self.listWidget.setSortingEnabled(False)
+ item = self.listWidget.item(0)
+ item.setText(_translate("MainWindow", "新建项目"))
+ item = self.listWidget.item(1)
+ item.setText(_translate("MainWindow", "新建项目"))
+ item = self.listWidget.item(2)
+ item.setText(_translate("MainWindow", "新建项目"))
+ item = self.listWidget.item(3)
+ item.setText(_translate("MainWindow", "新建项目"))
+ item = self.listWidget.item(4)
+ item.setText(_translate("MainWindow", "新建项目"))
+ self.listWidget.setSortingEnabled(__sortingEnabled)
+ self.menu_F.setTitle(_translate("MainWindow", "文件(F)"))
+ self.menu_data.setTitle(_translate("MainWindow", "数据"))
+ self.menu_output.setTitle(_translate("MainWindow", "导出聊天记录(全部)"))
+ self.menu_2.setTitle(_translate("MainWindow", "帮助"))
+ self.menu_about.setTitle(_translate("MainWindow", "关于"))
+ self.menu_3.setTitle(_translate("MainWindow", "不显示或者显示异常请重启应用、没反应那就多等一会儿"))
+ self.action_3.setText(_translate("MainWindow", "保存"))
+ self.action_4.setText(_translate("MainWindow", "退出"))
+ self.action_help_decrypt.setText(_translate("MainWindow", "解密教程"))
+ self.action_desc.setText(_translate("MainWindow", "说明"))
+ self.action_help_chat.setText(_translate("MainWindow", "聊天相关"))
+ self.action_help_contact.setText(_translate("MainWindow", "好友相关"))
+ self.action_output_CSV.setText(_translate("MainWindow", "CSV"))
+ self.action_output_contacts.setText(_translate("MainWindow", "导出联系人"))
diff --git a/app/ui/tool/__init__.py b/app/ui/tool/__init__.py
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/app/ui/tool/__init__.py
@@ -0,0 +1 @@
+
diff --git a/app/ui/tool/pc_decrypt/__init__.py b/app/ui/tool/pc_decrypt/__init__.py
new file mode 100644
index 00000000..ae1c6184
--- /dev/null
+++ b/app/ui/tool/pc_decrypt/__init__.py
@@ -0,0 +1,3 @@
+from .pc_decrypt import DecryptControl
+
+__all__ = ['DecryptControl']
diff --git a/app/ui/tool/pc_decrypt/decryptUi.py b/app/ui/tool/pc_decrypt/decryptUi.py
new file mode 100644
index 00000000..ac35c660
--- /dev/null
+++ b/app/ui/tool/pc_decrypt/decryptUi.py
@@ -0,0 +1,197 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'decryptUi.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ Dialog.setObjectName("Dialog")
+ Dialog.resize(611, 519)
+ font = QtGui.QFont()
+ font.setFamily("微软雅黑")
+ Dialog.setFont(font)
+ Dialog.setLayoutDirection(QtCore.Qt.LeftToRight)
+ self.verticalLayout_3 = QtWidgets.QVBoxLayout(Dialog)
+ self.verticalLayout_3.setObjectName("verticalLayout_3")
+ self.verticalLayout_2 = QtWidgets.QVBoxLayout()
+ self.verticalLayout_2.setObjectName("verticalLayout_2")
+ self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_4.setObjectName("horizontalLayout_4")
+ spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_4.addItem(spacerItem)
+ self.btn_help = QtWidgets.QPushButton(Dialog)
+ self.btn_help.setMaximumSize(QtCore.QSize(200, 16777215))
+ self.btn_help.setObjectName("btn_help")
+ self.horizontalLayout_4.addWidget(self.btn_help)
+ spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_4.addItem(spacerItem1)
+ self.verticalLayout_2.addLayout(self.horizontalLayout_4)
+ spacerItem2 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout_2.addItem(spacerItem2)
+ self.horizontalLayout_3 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_3.setObjectName("horizontalLayout_3")
+ spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_3.addItem(spacerItem3)
+ self.verticalLayout = QtWidgets.QVBoxLayout()
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.label_3 = QtWidgets.QLabel(Dialog)
+ font = QtGui.QFont()
+ font.setFamily("一纸情书")
+ font.setPointSize(20)
+ self.label_3.setFont(font)
+ self.label_3.setAlignment(QtCore.Qt.AlignCenter)
+ self.label_3.setObjectName("label_3")
+ self.verticalLayout.addWidget(self.label_3)
+ self.label_9 = QtWidgets.QLabel(Dialog)
+ self.label_9.setAlignment(QtCore.Qt.AlignCenter)
+ self.label_9.setObjectName("label_9")
+ self.verticalLayout.addWidget(self.label_9)
+ self.gridLayout_2 = QtWidgets.QGridLayout()
+ self.gridLayout_2.setObjectName("gridLayout_2")
+ self.gridLayout = QtWidgets.QGridLayout()
+ self.gridLayout.setObjectName("gridLayout")
+ self.label_phone = QtWidgets.QLabel(Dialog)
+ self.label_phone.setText("")
+ self.label_phone.setObjectName("label_phone")
+ self.gridLayout.addWidget(self.label_phone, 2, 1, 1, 1)
+ self.label_7 = QtWidgets.QLabel(Dialog)
+ self.label_7.setObjectName("label_7")
+ self.gridLayout.addWidget(self.label_7, 1, 0, 1, 1)
+ self.lineEdit = QtWidgets.QLineEdit(Dialog)
+ self.lineEdit.setStyleSheet("background:transparent;\n"
+"\n"
+" border-radius:5px;\n"
+" border-top: 0px solid #b2e281;\n"
+" border-bottom: 2px solid black;\n"
+" border-right: 0px solid #b2e281;\n"
+" border-left: 0px solid #b2e281;\n"
+"\n"
+"\n"
+" border-style:outset\n"
+" ")
+ self.lineEdit.setFrame(False)
+ self.lineEdit.setObjectName("lineEdit")
+ self.gridLayout.addWidget(self.lineEdit, 4, 1, 1, 1)
+ self.label_5 = QtWidgets.QLabel(Dialog)
+ self.label_5.setObjectName("label_5")
+ self.gridLayout.addWidget(self.label_5, 3, 0, 1, 1)
+ self.label_6 = QtWidgets.QLabel(Dialog)
+ self.label_6.setObjectName("label_6")
+ self.gridLayout.addWidget(self.label_6, 5, 0, 1, 1)
+ self.label_key = QtWidgets.QLabel(Dialog)
+ self.label_key.setMaximumSize(QtCore.QSize(400, 16777215))
+ self.label_key.setText("")
+ self.label_key.setObjectName("label_key")
+ self.gridLayout.addWidget(self.label_key, 5, 1, 1, 1)
+ self.label = QtWidgets.QLabel(Dialog)
+ self.label.setObjectName("label")
+ self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
+ self.label_2 = QtWidgets.QLabel(Dialog)
+ self.label_2.setObjectName("label_2")
+ self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
+ self.label_pid = QtWidgets.QLabel(Dialog)
+ self.label_pid.setText("")
+ self.label_pid.setObjectName("label_pid")
+ self.gridLayout.addWidget(self.label_pid, 0, 1, 1, 1)
+ self.label_name = QtWidgets.QLabel(Dialog)
+ self.label_name.setText("")
+ self.label_name.setObjectName("label_name")
+ self.gridLayout.addWidget(self.label_name, 3, 1, 1, 1)
+ self.label_4 = QtWidgets.QLabel(Dialog)
+ self.label_4.setObjectName("label_4")
+ self.gridLayout.addWidget(self.label_4, 4, 0, 1, 1)
+ self.label_version = QtWidgets.QLabel(Dialog)
+ self.label_version.setText("")
+ self.label_version.setObjectName("label_version")
+ self.gridLayout.addWidget(self.label_version, 1, 1, 1, 1)
+ self.label_8 = QtWidgets.QLabel(Dialog)
+ self.label_8.setObjectName("label_8")
+ self.gridLayout.addWidget(self.label_8, 6, 0, 1, 1)
+ self.label_db_dir = QtWidgets.QLabel(Dialog)
+ self.label_db_dir.setMaximumSize(QtCore.QSize(400, 300))
+ self.label_db_dir.setText("")
+ self.label_db_dir.setObjectName("label_db_dir")
+ self.gridLayout.addWidget(self.label_db_dir, 6, 1, 1, 1)
+ self.gridLayout.setColumnMinimumWidth(0, 1)
+ self.gridLayout.setColumnStretch(0, 1)
+ self.gridLayout.setColumnStretch(1, 10)
+ self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 2, 1)
+ self.btn_getinfo = QtWidgets.QPushButton(Dialog)
+ self.btn_getinfo.setMinimumSize(QtCore.QSize(0, 60))
+ self.btn_getinfo.setObjectName("btn_getinfo")
+ self.gridLayout_2.addWidget(self.btn_getinfo, 0, 1, 1, 1)
+ self.checkBox = QtWidgets.QCheckBox(Dialog)
+ self.checkBox.setText("")
+ self.checkBox.setObjectName("checkBox")
+ self.gridLayout_2.addWidget(self.checkBox, 0, 2, 1, 1)
+ self.btn_db_dir = QtWidgets.QPushButton(Dialog)
+ self.btn_db_dir.setMinimumSize(QtCore.QSize(0, 60))
+ self.btn_db_dir.setObjectName("btn_db_dir")
+ self.gridLayout_2.addWidget(self.btn_db_dir, 1, 1, 1, 1)
+ self.checkBox_2 = QtWidgets.QCheckBox(Dialog)
+ self.checkBox_2.setText("")
+ self.checkBox_2.setObjectName("checkBox_2")
+ self.gridLayout_2.addWidget(self.checkBox_2, 1, 2, 1, 1)
+ self.verticalLayout.addLayout(self.gridLayout_2)
+ self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
+ self.horizontalLayout_2.setObjectName("horizontalLayout_2")
+ spacerItem4 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_2.addItem(spacerItem4)
+ self.pushButton_3 = QtWidgets.QPushButton(Dialog)
+ self.pushButton_3.setMinimumSize(QtCore.QSize(0, 60))
+ self.pushButton_3.setMaximumSize(QtCore.QSize(100, 16777215))
+ self.pushButton_3.setObjectName("pushButton_3")
+ self.horizontalLayout_2.addWidget(self.pushButton_3)
+ self.label_tip = QtWidgets.QLabel(Dialog)
+ self.label_tip.setObjectName("label_tip")
+ self.horizontalLayout_2.addWidget(self.label_tip)
+ spacerItem5 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_2.addItem(spacerItem5)
+ self.verticalLayout.addLayout(self.horizontalLayout_2)
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.label_ready = QtWidgets.QLabel(Dialog)
+ self.label_ready.setObjectName("label_ready")
+ self.horizontalLayout.addWidget(self.label_ready)
+ self.progressBar = QtWidgets.QProgressBar(Dialog)
+ self.progressBar.setProperty("value", 50)
+ self.progressBar.setObjectName("progressBar")
+ self.horizontalLayout.addWidget(self.progressBar)
+ self.verticalLayout.addLayout(self.horizontalLayout)
+ self.horizontalLayout_3.addLayout(self.verticalLayout)
+ spacerItem6 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
+ self.horizontalLayout_3.addItem(spacerItem6)
+ self.verticalLayout_2.addLayout(self.horizontalLayout_3)
+ spacerItem7 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
+ self.verticalLayout_2.addItem(spacerItem7)
+ self.verticalLayout_3.addLayout(self.verticalLayout_2)
+
+ self.retranslateUi(Dialog)
+ QtCore.QMetaObject.connectSlotsByName(Dialog)
+
+ def retranslateUi(self, Dialog):
+ _translate = QtCore.QCoreApplication.translate
+ Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
+ self.btn_help.setText(_translate("Dialog", "使用说明"))
+ self.label_3.setText(_translate("Dialog", "解密数据库"))
+ self.label_9.setText(_translate("Dialog", "以下内容为自动获取,如获取失败请手动填写"))
+ self.label_7.setText(_translate("Dialog", "版本"))
+ self.label_5.setText(_translate("Dialog", "微信昵称"))
+ self.label_6.setText(_translate("Dialog", "密钥"))
+ self.label.setText(_translate("Dialog", "PID"))
+ self.label_2.setText(_translate("Dialog", "手机号"))
+ self.label_4.setText(_translate("Dialog", "wxid"))
+ self.label_8.setText(_translate("Dialog", "微信路径"))
+ self.btn_getinfo.setText(_translate("Dialog", "获取信息"))
+ self.btn_db_dir.setText(_translate("Dialog", "设置微信路径"))
+ self.pushButton_3.setText(_translate("Dialog", "开始启动"))
+ self.label_tip.setText(_translate("Dialog", "TextLabel"))
+ self.label_ready.setText(_translate("Dialog", "未就绪"))
diff --git a/app/ui/tool/pc_decrypt/pc_decrypt.py b/app/ui/tool/pc_decrypt/pc_decrypt.py
new file mode 100644
index 00000000..77f6b6a8
--- /dev/null
+++ b/app/ui/tool/pc_decrypt/pc_decrypt.py
@@ -0,0 +1,258 @@
+import json
+import os.path
+import time
+import traceback
+
+from PyQt5.QtCore import pyqtSignal, QThread, QUrl, QFile, QIODevice, QTextStream
+from PyQt5.QtGui import QDesktopServices
+from PyQt5.QtWidgets import QWidget, QMessageBox, QFileDialog
+
+from app.DataBase import msg_db, misc_db
+from app.DataBase.merge import merge_databases, merge_MediaMSG_databases
+from app.decrypt import get_wx_info, decrypt
+from app.log import logger
+from app.util import path
+from . import decryptUi
+
+
+class DecryptControl(QWidget, decryptUi.Ui_Dialog):
+ DecryptSignal = pyqtSignal(bool)
+ get_wxidSignal = pyqtSignal(str)
+
+ def __init__(self, parent=None):
+ super(DecryptControl, self).__init__(parent)
+ self.setupUi(self)
+
+ self.pushButton_3.clicked.connect(self.decrypt)
+ self.btn_getinfo.clicked.connect(self.get_info)
+ self.btn_db_dir.clicked.connect(self.select_db_dir)
+ self.lineEdit.returnPressed.connect(self.set_wxid)
+ self.lineEdit.textChanged.connect(self.set_wxid_)
+ self.btn_help.clicked.connect(self.show_help)
+ self.label_tip.setVisible(False)
+ self.info = {}
+ self.lineEdit.setFocus()
+ self.ready = False
+ self.wx_dir = None
+
+ def show_help(self):
+ # 定义网页链接
+ url = QUrl("https://blog.lc044.love/post/4")
+ # 使用QDesktopServices打开网页
+ QDesktopServices.openUrl(url)
+
+ # @log
+ def get_info(self):
+ try:
+ file = QFile(':/data/version_list.json')
+ if file.open(QIODevice.ReadOnly | QIODevice.Text):
+ stream = QTextStream(file)
+ content = stream.readAll()
+ file.close()
+ VERSION_LIST = json.loads(content)
+ else:
+ return
+ result = get_wx_info.get_info(VERSION_LIST)
+ print(result)
+ if result == -1:
+ QMessageBox.critical(self, "错误", "请登录微信")
+ elif result == -2:
+ QMessageBox.critical(self, "错误", "微信版本不匹配\n请更新微信版本为:3.9.8.15")
+ elif result == -3:
+ QMessageBox.critical(self, "错误", "WeChat WeChatWin.dll Not Found")
+ else:
+ self.ready = True
+ self.info = result[0]
+ self.label_key.setText(self.info['key'])
+ self.lineEdit.setText(self.info['wxid'])
+ self.label_name.setText(self.info['name'])
+ self.label_phone.setText(self.info['mobile'])
+ self.label_pid.setText(str(self.info['pid']))
+ self.label_version.setText(self.info['version'])
+ self.lineEdit.setFocus()
+ self.checkBox.setChecked(True)
+ self.get_wxidSignal.emit(self.info['wxid'])
+ directory = os.path.join(path.wx_path(), self.info['wxid'])
+ if os.path.exists(directory):
+ self.label_db_dir.setText(directory)
+ self.wx_dir = directory
+ self.checkBox_2.setChecked(True)
+ self.ready = True
+ if self.ready:
+ self.label_ready.setText('已就绪')
+ if self.wx_dir and os.path.exists(os.path.join(self.wx_dir)):
+ self.label_ready.setText('已就绪')
+ except Exception as e:
+ QMessageBox.critical(self, "未知错误", "请收集报错信息,发起issue解决问题")
+ logger.error(traceback.format_exc())
+
+ def set_wxid_(self):
+ self.info['wxid'] = self.lineEdit.text()
+
+ def set_wxid(self):
+ self.info['wxid'] = self.lineEdit.text()
+ QMessageBox.information(self, "ok", f"wxid修改成功{self.info['wxid']}")
+
+ def select_db_dir(self):
+ directory = QFileDialog.getExistingDirectory(
+ self, "选取微信文件保存目录——能看到Msg文件夹",
+ path.wx_path()
+ ) # 起始路径
+ db_dir = os.path.join(directory, 'Msg')
+ if not os.path.exists(db_dir):
+ QMessageBox.critical(self, "错误", "文件夹选择错误\n一般以wxid_xxx结尾")
+ return
+
+ self.label_db_dir.setText(directory)
+ self.wx_dir = directory
+ self.checkBox_2.setChecked(True)
+ if self.ready:
+ self.label_ready.setText('已就绪')
+
+ def decrypt(self):
+ if not self.ready:
+ QMessageBox.critical(self, "错误", "请先获取密钥")
+ return
+ if not self.wx_dir:
+ QMessageBox.critical(self, "错误", "请先选择微信安装路径")
+ return
+ if self.lineEdit.text() == 'None':
+ QMessageBox.critical(self, "错误", "请填入wxid")
+ return
+ db_dir = os.path.join(self.wx_dir, 'Msg')
+ if self.ready:
+ if not os.path.exists(db_dir):
+ QMessageBox.critical(self, "错误", "文件夹选择错误\n一般以wxid_xxx结尾")
+ return
+ if self.info.get('key') == 'none':
+ QMessageBox.critical(self, "错误", "密钥错误\n请检查微信版本是否为最新和微信路径是否正确")
+ self.label_tip.setVisible(True)
+ self.label_tip.setText('点我之后没有反应那就多等儿吧,不要再点了')
+ self.thread2 = DecryptThread(db_dir, self.info['key'])
+ self.thread2.maxNumSignal.connect(self.setProgressBarMaxNum)
+ self.thread2.signal.connect(self.progressBar_view)
+ self.thread2.okSignal.connect(self.btnExitClicked)
+ self.thread2.errorSignal.connect(
+ lambda x: QMessageBox.critical(self, "错误", "密钥错误\n请检查微信版本是否为最新和微信路径是否正确")
+ )
+ self.thread2.start()
+
+ def btnEnterClicked(self):
+ # print("enter clicked")
+ # 中间可以添加处理逻辑
+ # QMessageBox.about(self, "解密成功", "数据库文件存储在app/DataBase/Msg文件夹下")
+
+ self.DecryptSignal.emit(True)
+ # self.close()
+
+ def setProgressBarMaxNum(self, max_val):
+ self.progressBar.setRange(0, max_val)
+
+ def progressBar_view(self, value):
+ """
+ 进度条显示
+ :param value: 进度0-100
+ :return: None
+ """
+ self.progressBar.setProperty('value', value)
+ # self.btnExitClicked()
+ # data.init_database()
+
+ def btnExitClicked(self):
+ # print("Exit clicked")
+ dic = {
+ 'wxid': self.info['wxid'],
+ 'wx_dir': self.wx_dir,
+ 'name': self.info['name'],
+ 'mobile': self.info['mobile']
+ }
+ try:
+ os.makedirs('./app/data', exist_ok=True)
+ with open('./app/data/info.json', 'w', encoding='utf-8') as f:
+ f.write(json.dumps(dic))
+ except:
+ with open('./info.json', 'w', encoding='utf-8') as f:
+ f.write(json.dumps(dic))
+ # 目标数据库文件
+ target_database = "app/DataBase/Msg/MSG.db"
+ # 源数据库文件列表
+ source_databases = [f"app/DataBase/Msg/MSG{i}.db" for i in range(1, 200)]
+ import shutil
+ shutil.copy2("app/DataBase/Msg/MSG0.db", target_database) # 使用一个数据库文件作为模板
+ # 合并数据库
+ try:
+ merge_databases(source_databases, target_database)
+ except FileNotFoundError:
+ QMessageBox.critical(self, "错误", "数据库不存在\n请检查微信版本是否为最新")
+
+ # 音频数据库文件
+ target_database = "app/DataBase/Msg/MediaMSG.db"
+ # 源数据库文件列表
+ source_databases = [f"app/DataBase/Msg/MediaMSG{i}.db" for i in range(1, 20)]
+ shutil.copy2("app/DataBase/Msg/MediaMSG0.db", target_database) # 使用一个数据库文件作为模板
+ # 合并数据库
+ try:
+ merge_MediaMSG_databases(source_databases, target_database)
+ except FileNotFoundError:
+ QMessageBox.critical(self, "错误", "数据库不存在\n请检查微信版本是否为最新")
+
+ self.DecryptSignal.emit(True)
+ self.close()
+
+
+class DecryptThread(QThread):
+ signal = pyqtSignal(str)
+ maxNumSignal = pyqtSignal(int)
+ okSignal = pyqtSignal(str)
+ errorSignal = pyqtSignal(bool)
+
+ def __init__(self, db_path, key):
+ super(DecryptThread, self).__init__()
+ self.db_path = db_path
+ self.key = key
+ self.textBrowser = None
+
+ def __del__(self):
+ pass
+
+ def run(self):
+ misc_db.close()
+ msg_db.close()
+ # micro_msg_db.close()
+ # hard_link_db.close()
+ output_dir = 'app/DataBase/Msg'
+ os.makedirs(output_dir, exist_ok=True)
+ tasks = []
+ if os.path.exists(self.db_path):
+ for root, dirs, files in os.walk(self.db_path):
+ for file in files:
+ if '.db' == file[-3:]:
+ if 'xInfo.db' == file:
+ continue
+ inpath = os.path.join(root, file)
+ # print(inpath)
+ output_path = os.path.join(output_dir, file)
+ tasks.append([self.key, inpath, output_path])
+ self.maxNumSignal.emit(len(tasks))
+ for i, task in enumerate(tasks):
+ if decrypt.decrypt(*task) == -1:
+ self.errorSignal.emit(True)
+ self.signal.emit(str(i))
+ # print(self.db_path)
+ self.okSignal.emit('ok')
+ # self.signal.emit('100')
+
+
+class MyThread(QThread):
+ signal = pyqtSignal(str)
+
+ def __init__(self):
+ super(MyThread, self).__init__()
+
+ def __del__(self):
+ pass
+
+ def run(self):
+ for i in range(100):
+ self.signal.emit(str(i))
+ time.sleep(0.1)
diff --git a/app/ui/tool/toolUI.py b/app/ui/tool/toolUI.py
new file mode 100644
index 00000000..3f714817
--- /dev/null
+++ b/app/ui/tool/toolUI.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'toolUI.ui'
+#
+# Created by: PyQt5 UI code generator 5.15.7
+#
+# WARNING: Any manual changes made to this file will be lost when pyuic5 is
+# run again. Do not edit this file unless you know what you are doing.
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_Dialog(object):
+ def setupUi(self, Dialog):
+ Dialog.setObjectName("Dialog")
+ Dialog.resize(590, 547)
+ font = QtGui.QFont()
+ font.setFamily("微软雅黑")
+ Dialog.setFont(font)
+ self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.horizontalLayout = QtWidgets.QHBoxLayout()
+ self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint)
+ self.horizontalLayout.setSpacing(0)
+ self.horizontalLayout.setObjectName("horizontalLayout")
+ self.label = QtWidgets.QLabel(Dialog)
+ self.label.setMaximumSize(QtCore.QSize(80, 80))
+ self.label.setText("")
+ self.label.setObjectName("label")
+ self.horizontalLayout.addWidget(self.label)
+ self.listWidget = QtWidgets.QListWidget(Dialog)
+ self.listWidget.setMinimumSize(QtCore.QSize(100, 80))
+ self.listWidget.setMaximumSize(QtCore.QSize(500, 80))
+ self.listWidget.setFrameShape(QtWidgets.QFrame.NoFrame)
+ self.listWidget.setFrameShadow(QtWidgets.QFrame.Plain)
+ self.listWidget.setLineWidth(0)
+ self.listWidget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.listWidget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+ self.listWidget.setFlow(QtWidgets.QListView.LeftToRight)
+ self.listWidget.setObjectName("listWidget")
+ item = QtWidgets.QListWidgetItem()
+ self.listWidget.addItem(item)
+ item = QtWidgets.QListWidgetItem()
+ self.listWidget.addItem(item)
+ item = QtWidgets.QListWidgetItem()
+ self.listWidget.addItem(item)
+ item = QtWidgets.QListWidgetItem()
+ self.listWidget.addItem(item)
+ item = QtWidgets.QListWidgetItem()
+ self.listWidget.addItem(item)
+ self.horizontalLayout.addWidget(self.listWidget)
+ self.label_2 = QtWidgets.QLabel(Dialog)
+ self.label_2.setMaximumSize(QtCore.QSize(80, 80))
+ self.label_2.setText("")
+ self.label_2.setObjectName("label_2")
+ self.horizontalLayout.addWidget(self.label_2)
+ self.verticalLayout.addLayout(self.horizontalLayout)
+ self.stackedWidget = QtWidgets.QStackedWidget(Dialog)
+ self.stackedWidget.setObjectName("stackedWidget")
+ self.verticalLayout.addWidget(self.stackedWidget)
+ self.verticalLayout.setStretch(1, 1)
+
+ self.retranslateUi(Dialog)
+ self.stackedWidget.setCurrentIndex(-1)
+ QtCore.QMetaObject.connectSlotsByName(Dialog)
+
+ def retranslateUi(self, Dialog):
+ _translate = QtCore.QCoreApplication.translate
+ Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
+ __sortingEnabled = self.listWidget.isSortingEnabled()
+ self.listWidget.setSortingEnabled(False)
+ item = self.listWidget.item(0)
+ item.setText(_translate("Dialog", "新建项目"))
+ item = self.listWidget.item(1)
+ item.setText(_translate("Dialog", "新建项目"))
+ item = self.listWidget.item(2)
+ item.setText(_translate("Dialog", "新建项目"))
+ item = self.listWidget.item(3)
+ item.setText(_translate("Dialog", "新建项目"))
+ item = self.listWidget.item(4)
+ item.setText(_translate("Dialog", "新建项目"))
+ self.listWidget.setSortingEnabled(__sortingEnabled)
diff --git a/app/ui/tool/tool_window.py b/app/ui/tool/tool_window.py
new file mode 100644
index 00000000..b25a7baf
--- /dev/null
+++ b/app/ui/tool/tool_window.py
@@ -0,0 +1,89 @@
+from PyQt5.QtCore import Qt, pyqtSignal
+from PyQt5.QtGui import QFont
+from PyQt5.QtWidgets import QWidget, QListWidgetItem, QLabel
+
+from app.ui.Icon import Icon
+from .pc_decrypt import DecryptControl
+from .toolUI import Ui_Dialog
+
+# 美化样式表
+Stylesheet = """
+QPushButton{
+ background-color: #ffffff;
+}
+QPushButton:hover {
+ background-color: lightgray;
+}
+/*去掉item虚线边框*/
+QListWidget, QListView, QTreeWidget, QTreeView {
+ outline: 0px;
+ border:none;
+ background-color:rgb(240,240,240)
+}
+/*设置左侧选项的最小最大宽度,文字颜色和背景颜色*/
+QListWidget {
+ min-width: 400px;
+ max-width: 400px;
+ min-height: 80px;
+ max-height: 80px;
+ color: black;
+ border:none;
+}
+QListWidget::item{
+ height:80px;
+ width:80px;
+}
+/*被选中时的背景颜色和左边框颜色*/
+QListWidget::item:selected {
+ background: rgb(204, 204, 204);
+ border-bottom: 4px solid rgb(9, 187, 7);
+ border-left:none;
+ color: black;
+ font-weight: bold;
+}
+/*鼠标悬停颜色*/
+HistoryPanel::item:hover {
+ background: rgb(52, 52, 52);
+}
+"""
+
+
+class ToolWindow(QWidget, Ui_Dialog):
+ get_info_signal = pyqtSignal(str)
+ decrypt_success_signal = pyqtSignal(bool)
+ load_finish_signal = pyqtSignal(bool)
+
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setupUi(self)
+ self.setStyleSheet(Stylesheet)
+ self.init_ui()
+ self.load_finish_signal.emit(True)
+
+ def init_ui(self):
+ self.listWidget.clear()
+ self.listWidget.currentRowChanged.connect(self.setCurrentIndex)
+ chat_item = QListWidgetItem(Icon.Chat_Icon, '解密', self.listWidget)
+ contact_item = QListWidgetItem(Icon.Contact_Icon, '别点', self.listWidget)
+ myinfo_item = QListWidgetItem(Icon.MyInfo_Icon, '别点', self.listWidget)
+ tool_item = QListWidgetItem(Icon.MyInfo_Icon, '别点', self.listWidget)
+ decrypt_window = DecryptControl()
+ decrypt_window.get_wxidSignal.connect(self.get_info_signal)
+ decrypt_window.DecryptSignal.connect(self.decrypt_success_signal)
+ self.stackedWidget.addWidget(decrypt_window)
+ label = QLabel('都说了不让你点', self)
+ label.setFont(QFont("微软雅黑", 50))
+ label.setAlignment(Qt.AlignCenter)
+ # 设置label的背景颜色(这里随机)
+ # 这里加了一个margin边距(方便区分QStackedWidget和QLabel的颜色)
+ # label.setStyleSheet('background: rgb(%d, %d, %d);margin: 50px;' % (
+ # randint(0, 255), randint(0, 255), randint(0, 255)))
+ self.stackedWidget.addWidget(label)
+ self.stackedWidget.addWidget(label)
+ self.stackedWidget.addWidget(label)
+ self.listWidget.setCurrentRow(0)
+ self.stackedWidget.setCurrentIndex(0)
+
+ def setCurrentIndex(self, row):
+ print(row)
+ self.stackedWidget.setCurrentIndex(row)
diff --git a/app/util/path.py b/app/util/path.py
index 1f2ddbd4..e8d44398 100644
--- a/app/util/path.py
+++ b/app/util/path.py
@@ -1,7 +1,7 @@
import os
import winreg
-from app.person_pc import MePC
+from app.person import MePC
from app.util import dat2pic
os.makedirs('./data/image', exist_ok=True)
diff --git a/app/web_ui/web.py b/app/web_ui/web.py
index 77e8b7d8..5b724e2f 100644
--- a/app/web_ui/web.py
+++ b/app/web_ui/web.py
@@ -3,10 +3,9 @@
from flask import Flask, render_template, send_file
-
from app.DataBase import msg_db
from app.analysis import analysis
-from app.person_pc import ContactPC, MePC
+from app.person import ContactPC, MePC
app = Flask(__name__)
@@ -19,6 +18,7 @@ def index():
# 渲染模板,并传递图表的 HTML 到模板中
return render_template("index.html")
+
@app.route('/home')
def home():
try:
@@ -39,12 +39,12 @@ def one(who):
wxid = contact.wxid
# wxid = 'wxid_lltzaezg38so22'
# print('wxid:'+wxid)
- world_cloud_data = analysis.wordcloud(wxid,who=who) # 获取与Ta的对话数据
+ world_cloud_data = analysis.wordcloud(wxid, who=who) # 获取与Ta的对话数据
# print(world_cloud_data)
who = "你" if who == '1' else "TA"
with open('wordcloud.html', 'w', encoding='utf-8') as f:
f.write(render_template('wordcloud.html', **world_cloud_data))
- return render_template('wordcloud.html', **world_cloud_data,who=who)
+ return render_template('wordcloud.html', **world_cloud_data, who=who)
def set_text(text):