导出word文档

This commit is contained in:
shuaikangzhou 2023-01-23 09:47:40 +08:00
parent 885ef1c5a1
commit 78eb89aa38
13 changed files with 576 additions and 0 deletions

390
app/DataBase/output.py Normal file
View File

@ -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"<title>(.*?)<")
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>")
title = pattern1.search(content).groupdict()['title']
pattern2 = re.compile(r"<displayname>(?P<displayname>(.*?))</displayname>")
displayname = pattern2.search(content).groupdict()['displayname']
'''匹配回复的回复'''
pattern3 = re.compile(r"\n?title&gt;(?P<content>(.*?))\n?&lt;/title&gt")
if not pattern3.search(content):
if isSend == 0:
'''匹配对方的回复'''
pattern3 = re.compile(r"<content>(?P<content>(.*?))</content>")
else:
'''匹配自己的回复'''
pattern3 = re.compile(r"</msgsource>\n?<content>(?P<content>(.*?))\n?</content>")
'''这部分代码完全可以用if代替'''
try:
'''试错'''
text = pattern3.search(content).groupdict()['content']
except Exception:
try:
'''试错'''
text = pattern3.search(content).groupdict()['content']
except Exception:
'''试错'''
pattern3 = re.compile(r"\n?<content>(?P<content>(.*?))\n?</content>")
'''试错'''
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()

0
app/ImageBox/__init__.py Normal file
View File

6
app/ImageBox/config.py Normal file
View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 543 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

9
app/ImageBox/run.py Normal file
View File

@ -0,0 +1,9 @@
from ui import MainDemo
from config import *
if __name__ == '__main__':
app = QApplication(sys.argv)
box = MainDemo()
box.show()
app.exec_()

157
app/ImageBox/ui.py Normal file
View File

@ -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()

9
app/data/__init__.py Normal file
View File

@ -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 : ···
"""

5
app/data/config.json Normal file
View File

@ -0,0 +1,5 @@
{
"username": "root",
"password": "123456",
"database": "chat_2020303457"
}

BIN
app/data/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB