diff --git a/app/DataBase/output.py b/app/DataBase/output.py
new file mode 100644
index 0000000..dd6fe62
--- /dev/null
+++ b/app/DataBase/output.py
@@ -0,0 +1,390 @@
+import os
+import re
+import time
+import docx
+import pandas as pd
+import requests
+import xmltodict
+from docx import shared
+from docx.enum.table import WD_ALIGN_VERTICAL
+from docx.enum.text import WD_COLOR_INDEX, WD_PARAGRAPH_ALIGNMENT
+from docxcompose.composer import Composer
+from PyQt5.QtWidgets import *
+from PyQt5.QtCore import *
+from PyQt5.QtGui import *
+from . import data
+
+
+def IS_5_min(last_m, now_m):
+ '''
+ #! 判断两次聊天时间是不是大于五分钟
+ #! 若大于五分钟则显示时间
+ #! 否则不显示
+ '''
+ '''两次聊天记录时间差,单位是秒'''
+ dt = now_m - last_m
+ return abs(dt // 1000) >= 300
+
+
+def time_format(timestamp):
+ '''
+ #! 将字符串类型的时间戳转换成日期
+ #! 返回格式化的时间字符串
+ #! %Y-%m-%d %H:%M:%S
+ '''
+ timestamp = timestamp / 1000
+ time_tuple = time.localtime(timestamp)
+ return time.strftime("%Y-%m-%d %H:%M:%S", time_tuple)
+
+
+def merge_docx(conRemark, n):
+ origin_docx_path = f"{path}/{conRemark}"
+ all_word = os.listdir(origin_docx_path)
+ all_file_path = []
+ for i in range(n):
+ file_name = f"{conRemark}{i}.docx"
+ all_file_path.append(origin_docx_path + '/' + file_name)
+ filename = f"{conRemark}.docx"
+ # print(all_file_path)
+ doc = docx.Document()
+ doc.save(origin_docx_path + '/' + filename)
+ master = docx.Document(origin_docx_path + '/' + filename)
+ middle_new_docx = Composer(master)
+ num = 0
+ for word in all_file_path:
+ word_document = docx.Document(word)
+ word_document.add_page_break()
+ if num != 0:
+ middle_new_docx.append(word_document)
+ num = num + 1
+ middle_new_docx.save(origin_docx_path + '/' + filename)
+
+
+class Output(QThread):
+ """
+ 发送信息线程
+ """
+ progressSignal = pyqtSignal(int)
+ successSignal = pyqtSignal(int)
+
+ def __init__(self, Me, ta_u, parent=None):
+ super().__init__(parent)
+ self.Me = Me
+ self.sec = 2 # 默认1000秒
+ self.ta_username = ta_u
+ self.my_avatar = self.Me.my_avatar
+ self.ta_avatar = data.get_avator(ta_u)
+ self.msg_id = 0
+
+ def create_table(self, doc, isSend):
+ '''
+ #! 创建一个1*2表格
+ #! isSend = 1 (0,0)存聊天内容,(0,1)存头像
+ #! isSend = 0 (0,0)存头像,(0,1)存聊天内容
+ #! 返回聊天内容的坐标
+ '''
+ table = doc.add_table(rows=1, cols=2, style='Normal Table')
+ table.cell(0, 1).height = shared.Inches(0.5)
+ table.cell(0, 0).height = shared.Inches(0.5)
+ text_size = 1
+ if isSend:
+ '''表格右对齐'''
+ table.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
+ avatar = table.cell(0, 1).paragraphs[0].add_run()
+ '''插入头像,设置头像宽度'''
+ avatar.add_picture(self.my_avatar, width=shared.Inches(0.5))
+ '''设置单元格宽度跟头像一致'''
+ table.cell(0, 1).width = shared.Inches(0.5)
+ content_cell = table.cell(0, 0)
+ '''聊天内容右对齐'''
+ content_cell.paragraphs[0].paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
+ else:
+ avatar = table.cell(0, 0).paragraphs[0].add_run()
+ avatar.add_picture(self.ta_avatar, width=shared.Inches(0.5))
+ '''设置单元格宽度'''
+ table.cell(0, 0).width = shared.Inches(0.5)
+ content_cell = table.cell(0, 1)
+ '''聊天内容垂直居中对齐'''
+ content_cell.vertical_alignment = WD_ALIGN_VERTICAL.CENTER
+ return content_cell
+
+ def text(self, doc, isSend, message, status):
+ if status == 5:
+ message += '(未发出) '
+ content_cell = self.create_table(doc, isSend)
+ content_cell.paragraphs[0].add_run(message)
+ content_cell.paragraphs[0].font_size = shared.Inches(0.5)
+ # self.self_text.emit(message)
+ if isSend:
+ p = content_cell.paragraphs[0]
+ p.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
+ doc.add_paragraph()
+
+ def image(self, doc, isSend, Type, content, imgPath):
+ '''
+ #! 插入聊天图片
+ #! isSend = 1 只有缩略图
+ #! isSend = 0 有原图
+ :param doc:
+ :param isSend:
+ :param Type:
+ :param content:
+ :param imgPath:
+ :return:
+ '''
+ content = self.create_table(doc, isSend)
+ run = content.paragraphs[0].add_run()
+ if Type == 3:
+ imgPath = imgPath.split('th_')[1]
+ imgPath = f'./app/data/image2/{imgPath[0:2]}/{imgPath[2:4]}/th_{imgPath}'
+ imgPath = data.clearImagePath(imgPath)
+ try:
+ run.add_picture(f'{imgPath}', height=shared.Inches(2))
+ doc.add_paragraph()
+ except Exception:
+ print("Error!image")
+
+ # run.add_picture(f'{Path}/{imgPath}', height=shared.Inches(2))
+
+ def emoji(self, doc, isSend, content, imgPath):
+ '''
+ #! 添加表情包
+ :param isSend:
+ :param content:
+ :param imgPath:
+ :return:
+ '''
+ imgPath = data.get_emoji(imgPath)
+ if 1:
+ is_Exist = os.path.exists(imgPath)
+ self.image(doc, isSend, Type=47, content=content, imgPath=imgPath)
+
+ def wx_file(self, doc, isSend, content, status):
+ '''
+ #! 添加微信文件
+ :param isSend:
+ :param content:
+ :param status:
+ :return:
+ '''
+ pattern = re.compile(r"
(.*?)<")
+ r = pattern.search(content).group()
+ filename = r.lstrip('').rstrip('<')
+ self.text(doc, isSend, filename, status)
+
+ def retract_message(self, doc, isSend, content, status):
+ '''
+ #! 显示撤回消息
+ :param isSend:
+ :param content:
+ :param status:
+ :return:
+ '''
+ paragraph = doc.add_paragraph(content)
+ paragraph.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
+
+ def reply(self, doc, isSend, content, status):
+ '''
+ #! 添加回复信息
+ :param isSend:
+ :param content:
+ :param status:
+ :return:
+ '''
+ pattern1 = re.compile(r"(?P(.*?))")
+ title = pattern1.search(content).groupdict()['title']
+ pattern2 = re.compile(r"(?P(.*?))")
+ displayname = pattern2.search(content).groupdict()['displayname']
+ '''匹配回复的回复'''
+ pattern3 = re.compile(r"\n?title>(?P(.*?))\n?</title>")
+ if not pattern3.search(content):
+ if isSend == 0:
+ '''匹配对方的回复'''
+ pattern3 = re.compile(r"(?P(.*?))")
+ else:
+ '''匹配自己的回复'''
+ pattern3 = re.compile(r"\n?(?P(.*?))\n?")
+
+ '''这部分代码完全可以用if代替'''
+
+ try:
+ '''试错'''
+ text = pattern3.search(content).groupdict()['content']
+ except Exception:
+ try:
+ '''试错'''
+ text = pattern3.search(content).groupdict()['content']
+ except Exception:
+ '''试错'''
+ pattern3 = re.compile(r"\n?(?P(.*?))\n?")
+ '''试错'''
+ if pattern3.search(content):
+ text = pattern3.search(content).groupdict()['content']
+ else:
+ text = '图片'
+ if status == 5:
+ message = '(未发出) ' + ''
+ content_cell = self.create_table(doc, isSend)
+ content_cell.paragraphs[0].add_run(title)
+ content_cell.paragraphs[0].font_size = shared.Inches(0.5)
+ reply_p = content_cell.add_paragraph()
+ run = content_cell.paragraphs[1].add_run(displayname + ':' + text)
+ '''设置被回复内容格式'''
+ run.font.color.rgb = shared.RGBColor(121, 121, 121)
+ run.font_size = shared.Inches(0.3)
+ run.font.highlight_color = WD_COLOR_INDEX.GRAY_25
+
+ if isSend:
+ p = content_cell.paragraphs[0]
+ p.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
+ reply_p.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.RIGHT
+ doc.add_paragraph()
+
+ def pat_a_pat(self, doc, isSend, content, status):
+ '''
+ #! 添加拍一拍信息
+ todo 把wxid转化成昵称
+ :param isSend:
+ :param content:
+ :param status:
+ :return:
+ '''
+ pat_data = xmltodict.parse(content)
+ pat_data = pat_data['msg']['appmsg']['patMsg']['records']['record']
+ fromUser = pat_data['fromUser']
+ pattedUser = pat_data['pattedUser']
+ template = pat_data['template']
+ template = ''.join(template.split('${pattedusername@textstatusicon}'))
+ template = ''.join(template.split('${fromusername@textstatusicon}'))
+ template = template.replace(f'${{{fromUser}}}', data.get_conRemark(fromUser))
+ template = template.replace(f'${{{pattedUser}}}', data.get_conRemark(pattedUser))
+ print(template)
+ p = doc.add_paragraph()
+ run = p.add_run(template)
+ p.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
+ '''设置拍一拍文字格式'''
+ run.font.color.rgb = shared.RGBColor(121, 121, 121)
+ run.font_size = shared.Inches(0.3)
+ # run.font.highlight_color=WD_COLOR_INDEX.GRAY_25
+
+ def video(self, doc, isSend, content, status, img_path):
+ print(content, img_path)
+
+ def to_docx(self, messages, i, conRemark):
+ '''创建联系人目录'''
+ data.mkdir(f"{os.path.abspath('.')}/data/聊天记录/{conRemark}")
+ filename = f"{os.path.abspath('.')}/data/聊天记录/{conRemark}/{conRemark}{i}.docx"
+ doc = docx.Document()
+ last_timestamp = 1601968667000
+ for message in messages:
+ msgId = message[0]
+ ta_username = message[7]
+ Type = int(message[2])
+ isSend = message[4]
+ content = message[8]
+ imgPath = message[9]
+ now_timestamp = message[6]
+ status = message[3]
+ createTime = time_format(now_timestamp)
+ # print(createTime, isSend, content)
+ if IS_5_min(last_timestamp, now_timestamp):
+ doc.add_paragraph(createTime).alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
+ last_timestamp = now_timestamp
+ if Type == 1:
+ try:
+ self.text(doc, isSend, content, status)
+ except Exception as e:
+ print(e)
+ elif Type == 3:
+ self.image(doc, isSend, 3, content, imgPath)
+ elif Type == 47:
+ self.emoji(doc, isSend, content, imgPath)
+ elif Type == 1090519089:
+ self.wx_file(doc, isSend, content, status)
+ elif Type == 268445456:
+ self.retract_message(doc, isSend, content, status)
+ elif Type == 822083633:
+ self.reply(doc, isSend, content, status)
+ elif Type == 922746929:
+ self.pat_a_pat(doc, isSend, content, status)
+ elif Type == 43:
+ # print(createTime)
+ self.video(doc, isSend, content, status, imgPath)
+ # doc.add_paragraph(str(i))
+ print(filename)
+ doc.save(filename)
+
+ def run(self):
+ if 1:
+ conRemark = data.get_conRemark(self.ta_username)
+ messages = data.get_all_message(self.ta_username)
+ # self.self_text.emit(conRemark)
+ # self.self_text.emit(path)
+ self.to_docx(messages, 0, conRemark)
+ # l = len(user_data)
+ # n = 50
+ # for i in range(n):
+ # q = i * (l // n)
+ # p = (i + 1) * (l // n)
+ # if i == n - 1:
+ # p = l
+ # len_data = user_data[q:p]
+ # self.to_docx(len_data, i, conRemark)
+
+ # self.self_text.emit('\n\n\n导出进度还差一点点!!!')
+ # self.bar.emit(99)
+ # merge_docx(conRemark, n)
+ # self.self_text.emit(f'{conRemark}聊天记录导出成功!!!')
+ # self.bar.emit(100)
+
+ # def run(self):
+ # self.ta_avatar = data.get_avator(self.ta_u)
+ # messages = data.get_all_message(self.ta_u)
+ # total_num = len(messages)
+ # for message in messages:
+ # msgId = message[0]
+ # ta_username = message[7]
+ # msgType = str(message[2])
+ # isSend = message[4]
+ # content = message[8]
+ # imgPath = message[9]
+ # msg_time = message[6]
+ # self.check_time(msg_time)
+ #
+ # if msgType == '1':
+ # # return
+ # self.show_text(isSend, content)
+ # elif msgType == '3':
+ # # return
+ # self.show_img(isSend, imgPath, content)
+ # elif msgType == '47':
+ # # return
+ # self.show_emoji(isSend, imgPath, content)
+ # elif msgType == '268445456':
+ # self.show_recall_information(content)
+ # elif msgType == '922746929':
+ # self.pat_a_pat(content)
+
+
+if __name__ == '__main__':
+ # # conRemark = '张三' #! 微信备注名
+ # n = 100 # ! 分割的文件个数
+ # main(conRemark, n)
+ # img_self.close()
+ # img_ta.close()
+ me = data.Me('wxid_27hqbq7vx5hf22')
+ t = Output(Me=me, ta_u='wxid_q3ozn70pweud22')
+ # t.ta_info = {
+ # 'wxid': 'wxid_q3ozn70pweud22',
+ # 'conRemark': '小钱'
+ # }
+ # t.ta_info = {
+ # 'wxid': 'wxid_8piw6sb4hvfm22',
+ # 'conRemark': '曹雨萱'
+ # }
+ # # wxid_8piw6sb4hvfm22
+ # t.self_info = {
+ # 'wxid': 'wxid_27hqbq7vx5hf22',
+ # 'conRemark': 'Shuaikang Zhou'
+ # }
+ t.run()
diff --git a/app/ImageBox/__init__.py b/app/ImageBox/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/app/ImageBox/config.py b/app/ImageBox/config.py
new file mode 100644
index 0000000..cdebb51
--- /dev/null
+++ b/app/ImageBox/config.py
@@ -0,0 +1,6 @@
+import sys
+from PyQt5.QtWidgets import QWidget, QApplication, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QFileDialog
+from PyQt5.Qt import QPixmap, QPoint, Qt, QPainter, QIcon
+from PyQt5.QtCore import QSize
+from PyQt5 import QtCore, QtGui, QtWidgets
+from PyQt5.QtGui import QImageReader
diff --git a/app/ImageBox/icons/zoom_in.jpg b/app/ImageBox/icons/zoom_in.jpg
new file mode 100644
index 0000000..3d8378b
Binary files /dev/null and b/app/ImageBox/icons/zoom_in.jpg differ
diff --git a/app/ImageBox/icons/zoom_out.jpg b/app/ImageBox/icons/zoom_out.jpg
new file mode 100644
index 0000000..fde5376
Binary files /dev/null and b/app/ImageBox/icons/zoom_out.jpg differ
diff --git a/app/ImageBox/images/wallhaven-748705.jpg b/app/ImageBox/images/wallhaven-748705.jpg
new file mode 100644
index 0000000..15b690a
Binary files /dev/null and b/app/ImageBox/images/wallhaven-748705.jpg differ
diff --git a/app/ImageBox/images/wallhaven-753155.jpg b/app/ImageBox/images/wallhaven-753155.jpg
new file mode 100644
index 0000000..5139dc4
Binary files /dev/null and b/app/ImageBox/images/wallhaven-753155.jpg differ
diff --git a/app/ImageBox/images/wallhaven-vml6em.jpg b/app/ImageBox/images/wallhaven-vml6em.jpg
new file mode 100644
index 0000000..233957f
Binary files /dev/null and b/app/ImageBox/images/wallhaven-vml6em.jpg differ
diff --git a/app/ImageBox/run.py b/app/ImageBox/run.py
new file mode 100644
index 0000000..a3671c7
--- /dev/null
+++ b/app/ImageBox/run.py
@@ -0,0 +1,9 @@
+from ui import MainDemo
+from config import *
+
+
+if __name__ == '__main__':
+ app = QApplication(sys.argv)
+ box = MainDemo()
+ box.show()
+ app.exec_()
diff --git a/app/ImageBox/ui.py b/app/ImageBox/ui.py
new file mode 100644
index 0000000..5894631
--- /dev/null
+++ b/app/ImageBox/ui.py
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+from .config import *
+
+
+class ImageBox(QWidget):
+ def __init__(self):
+ super(ImageBox, self).__init__()
+ self.img = None
+ self.scaled_img = None
+ self.point = QPoint(100, 100)
+ self.start_pos = None
+ self.end_pos = None
+ self.left_click = False
+ self.scale = 1
+
+ def init_ui(self):
+ self.setWindowTitle("ImageBox")
+
+ def set_image(self, img_path):
+ """
+ open image file
+ :param img_path: image file path
+ :return:
+ """
+ # img = QImageReader(img_path)
+ # img.setScaledSize(QSize(self.size().width(), self.size().height()))
+ # img = img.read()
+ self.img = QPixmap(img_path)
+ # print(self.img.size(),self.img.size().width(),self.img.size().height())
+ self.scaled_img = self.img
+ # print(img_size)
+ img_size = self.scaled_img.size()
+ x = min(500, max((1000 - img_size.width()) // 2, 0))
+ y = min(300, max((600 - img_size.height()) // 2-60, 0))
+ # print(x,y)
+ self.point = QPoint(x, y)
+
+ def paintEvent(self, e):
+ """
+ receive paint events
+ :param e: QPaintEvent
+ :return:
+ """
+ if self.scaled_img:
+ painter = QPainter()
+ painter.begin(self)
+ painter.scale(self.scale, self.scale)
+ painter.drawPixmap(self.point, self.scaled_img)
+ painter.end()
+
+ def wheelEvent(self, event):
+ angle = event.angleDelta() / 8 # 返回QPoint对象,为滚轮转过的数值,单位为1/8度
+ angleY = angle.y()
+ # 获取当前鼠标相对于view的位置
+ if angleY > 0:
+ self.scale *= 1.1
+ else: # 滚轮下滚
+ self.scale *= 0.9
+ self.adjustSize()
+ self.update()
+
+ def mouseMoveEvent(self, e):
+ """
+ mouse move events for the widget
+ :param e: QMouseEvent
+ :return:
+ """
+ if self.left_click:
+ self.end_pos = e.pos() - self.start_pos
+ self.point = self.point + self.end_pos
+ self.start_pos = e.pos()
+ self.repaint()
+
+ def mousePressEvent(self, e):
+ """
+ mouse press events for the widget
+ :param e: QMouseEvent
+ :return:
+ """
+ if e.button() == Qt.LeftButton:
+ self.left_click = True
+ self.start_pos = e.pos()
+
+ def mouseReleaseEvent(self, e):
+ """
+ mouse release events for the widget
+ :param e: QMouseEvent
+ :return:
+ """
+ if e.button() == Qt.LeftButton:
+ self.left_click = False
+
+
+class MainDemo(QWidget):
+ def __init__(self):
+ super(MainDemo, self).__init__()
+
+ self.setWindowTitle("Image Viewer")
+ self.setFixedSize(1000, 600)
+ self.setWindowIcon(QIcon('./app/data/icon.png'))
+ self.zoom_in = QPushButton("")
+ self.zoom_in.clicked.connect(self.large_click)
+ self.zoom_in.setFixedSize(30, 30)
+ in_icon = QIcon("./app/ImageBox/icons/zoom_in.jpg")
+ self.zoom_in.setIcon(in_icon)
+ self.zoom_in.setIconSize(QSize(30, 30))
+
+ self.zoom_out = QPushButton("")
+ self.zoom_out.clicked.connect(self.small_click)
+ self.zoom_out.setFixedSize(30, 30)
+ out_icon = QIcon("./app/ImageBox/icons/zoom_out.jpg")
+ self.zoom_out.setIcon(out_icon)
+ self.zoom_out.setIconSize(QSize(30, 30))
+
+ w = QWidget(self)
+ layout = QHBoxLayout()
+ layout.addWidget(self.zoom_in)
+ layout.addWidget(self.zoom_out)
+ layout.setAlignment(Qt.AlignLeft)
+ w.setLayout(layout)
+ w.setFixedSize(550, 50)
+
+ self.box = ImageBox()
+ self.box.resize(500, 300)
+
+ layout = QVBoxLayout()
+ layout.addWidget(w)
+ layout.addWidget(self.box)
+ self.setLayout(layout)
+
+ def open_image(self):
+ """
+ select image file and open it
+ :return:
+ """
+ img_name, _ = QFileDialog.getOpenFileName(self, "Open Image File", "*.jpg;;*.png;;*.jpeg")
+ self.box.set_image(img_name)
+
+ def large_click(self):
+ """
+ used to enlarge image
+ :return:
+ """
+ if self.box.scale < 2:
+ self.box.scale += 0.1
+ self.box.adjustSize()
+ self.update()
+
+ def small_click(self):
+ """
+ used to reduce image
+ :return:
+ """
+ if self.box.scale > 0.1:
+ self.box.scale -= 0.2
+ self.box.adjustSize()
+ self.update()
diff --git a/app/data/__init__.py b/app/data/__init__.py
new file mode 100644
index 0000000..64d5f6a
--- /dev/null
+++ b/app/data/__init__.py
@@ -0,0 +1,9 @@
+# -*- coding: utf-8 -*-
+"""
+@File : __init__.py.py
+@Author : Shuaikang Zhou
+@Time : 2022/12/13 14:19
+@IDE : Pycharm
+@Version : Python3.10
+@comment : ···
+"""
diff --git a/app/data/config.json b/app/data/config.json
new file mode 100644
index 0000000..acfb270
--- /dev/null
+++ b/app/data/config.json
@@ -0,0 +1,5 @@
+{
+ "username": "root",
+ "password": "123456",
+ "database": "chat_2020303457"
+}
\ No newline at end of file
diff --git a/app/data/icon.png b/app/data/icon.png
new file mode 100644
index 0000000..8e6590f
Binary files /dev/null and b/app/data/icon.png differ