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('<title>').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"<title>(?P<title>(.*?))") + 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