From dec7bd0be78e216009388810e5ac0209aaf419bb Mon Sep 17 00:00:00 2001 From: wuyanyun Date: Sat, 23 Dec 2023 21:50:57 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0html=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=96=87=E4=BB=B6=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/DataBase/output_pc.py | 100 +++++++++++++++++++++++++++++--- app/resources/icons/file.png | Bin 0 -> 568 bytes app/ui/contact/export_dialog.py | 3 + app/util/file.py | 63 ++++++++++++++++++++ 4 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 app/resources/icons/file.png create mode 100644 app/util/file.py diff --git a/app/DataBase/output_pc.py b/app/DataBase/output_pc.py index b6c509f..2fbaefe 100644 --- a/app/DataBase/output_pc.py +++ b/app/DataBase/output_pc.py @@ -16,6 +16,7 @@ import shutil from ..util.compress_content import parser_reply from ..util.emoji import get_emoji +from ..util.file import get_file os.makedirs('./data/聊天记录', exist_ok=True) @@ -138,10 +139,10 @@ class Output(QThread): self.requestInterruption() -def modify_audio_metadata(audiofile, new_artist): # 修改音频元数据中的“创作者”标签 +def modify_audio_metadata(audiofile, new_artist): # 修改音频元数据中的“创作者”标签 return audiofile = load(audiofile) - + # 检查文件是否有标签 if audiofile.tag is None: audiofile.initTag() @@ -254,7 +255,7 @@ class ChildThread(QThread): try: os.utime(origin_docx_path + image_path[1:], (timestamp, timestamp)) except: - print("网络图片",image_path) + print("网络图片", image_path) pass image_path = image_path.replace('\\', '/') # print(f"tohtml:---{image_path}") @@ -354,8 +355,41 @@ class ChildThread(QThread): f'''{str_time} {name}\n[表情包]\n\n''' ) - def wx_file(self, doc, isSend, content, status): - return + def file(self, doc, message): + origin_docx_path = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}" + bytesExtra = message[10] + str_time = message[8] + is_send = message[4] + timestamp = message[5] + is_chatroom = 1 if self.contact.is_chatroom else 0 + if is_chatroom: + avatar = f"./avatar/{message[12].wxid}.png" + else: + avatar = f"./avatar/{MePC().wxid if is_send else self.contact.wxid}.png" + if is_chatroom: + if is_send: + displayname = MePC().name + else: + displayname = message[12].remark + else: + displayname = MePC().name if is_send else self.contact.remark + displayname = escape_js_and_html(displayname) + if self.output_type == Output.HTML: + link = get_file(bytesExtra, thumb=True, output_path=origin_docx_path + '/file') + file_name = '' + shutil.copy(f"{os.path.abspath('.')}/app/resources/icons/file.png", origin_docx_path + '/file/file.png') + file_path = './file/file.png' + if link != "": + file_name = os.path.basename(link) + link = './file/' + file_name + + if self.is_5_min(timestamp): + doc.write( + f'''{{ type:0, text: '{str_time}',is_send:0,avatar_path:'',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{displayname}'}},''' + ) + doc.write( + f'''{{ type:49, text: '{file_path}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{displayname}',link: '{link}',sub_type:6,file_name: '{file_name}'}},''' + ) def retract_message(self, doc, isSend, content, status): return @@ -424,7 +458,8 @@ class ChildThread(QThread): str_time = message[8] timestamp = message[5] is_chatroom = 1 if self.contact.is_chatroom else 0 - str_content = str_content.replace('重新编辑]]>', "") + str_content = str_content.replace('重新编辑]]>', "") res = findall('()', str_content) for xmlstr, b in res: str_content = str_content.replace(xmlstr, "") @@ -542,7 +577,7 @@ class ChildThread(QThread): MePC().avatar.save(os.path.join(f"{origin_docx_path}/avatar/{MePC().wxid}.png")) if self.contact.is_chatroom: for message in messages: - if message[4]: # is_send + if message[4]: # is_send continue try: chatroom_avatar_path = f"{origin_docx_path}/avatar/{message[12].wxid}.png" @@ -572,6 +607,8 @@ class ChildThread(QThread): self.system_msg(f, message) elif type_ == 49 and sub_type == 57 and self.message_types.get(1): self.refermsg(f, message) + elif type_ == 49 and sub_type == 6 and self.message_types.get(4906): + self.file(f, message) f.write(html_end) f.close() self.okSignal.emit(1) @@ -893,7 +930,7 @@ body{ color: darkgray; } -.chat-image img{ +.chat-image img,.chat-file img{ margin-right: 18px; margin-left: 18px; max-width: 300px; @@ -916,6 +953,29 @@ audio{ margin-left: 9px; margin-right: 9px; } + +.chat-file { + width: 300px; + background-color: #fff; + margin-right: 20px; +} +.chat-file a ,.chat-file div{ + display: flex; + color: #000; + outline: none; + text-decoration: none; + margin: 0 20px 20px 20px; +} +.chat-file div{ + margin: 20px; +} +.chat-file a span ,.chat-file div span{ + /* flex-grow: 1; */ + width: 200px; +} +.chat-file a img,.chat-file div img{ + width: 100px; +} .input-area{ border-top:0.5px solid #e0e0e0; height: 150px; @@ -1139,6 +1199,17 @@ html_end = ''' messageAudioTag.innerHTML = ``; return messageAudioTag; } + function messageFileBox(message) { + const messageFileTag = document.createElement('div'); + messageFileTag.className = `chat-file`; + if (message.link !== ''){ + messageFileTag.innerHTML = ` + ${message.file_name}` + }else{ + messageFileTag.innerHTML = `
文件已丢失
`; + } + return messageFileTag; + } // 从数据列表中取出对应范围的元素并添加到容器中 for (let i = startIndex; i < endIndex && i < chatMessages.length; i++) { @@ -1224,6 +1295,19 @@ html_end = ''' messageContent.appendChild(messageElementReferText(message, side)); } + // 整合 + messageElement.className = `item item-${side}`; + messageElement.appendChild(message.is_send ? messageContent : avatarTag); + messageElement.appendChild(message.is_send ? avatarTag : messageContent); + } + if (message.sub_type == 6) { + // displayname 和 file + messageContent.className = `content-wrapper content-wrapper-${side}`; + if (message.is_chatroom && !message.is_send) { + messageContent.appendChild(displayNameBox(message)); + } + messageContent.appendChild(messageFileBox(message)); + // 整合 messageElement.className = `item item-${side}`; messageElement.appendChild(message.is_send ? messageContent : avatarTag); diff --git a/app/resources/icons/file.png b/app/resources/icons/file.png new file mode 100644 index 0000000000000000000000000000000000000000..565cf36d3ce1a2950988b296684b68e897a27199 GIT binary patch literal 568 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5M?jcysy3fA0|VnL zPZ!6KiaBp*`)4s3inPanI`&*%#Bs)&rQsh~Y`ystwR3a5`!8-TS-q&EgIhfAK(Dl* zY@u50lxe#%4d;GbS$W^?;dFg{V^0N!CIt?;#YV5pUwr?4eQA*9i535C1um`&TOGOh z;tbhaX59@}-j>O~`ueMOV+706O+|ao7wxQJZE$DU(7wexnxWKcZka@z?%L8*vH$%h zsc_k8&+=vXTD5nZ;j-2a0hWUo)Bf8pQEgyrJ{T;~R^IBwI7x-k#b@Q~98YHk-uA=k z$!Buy=Cdm`X>>VgK6PcikYRF5+D7nT*=}FeJBB~{8KnA;>mQr(?D~V^oiYlKBG3F0 zU&oYTG;`0DtgUG>$2Y{SSL{-9aJdi_zlXteu3!GDt69a{Z#xRG+;84?t!b-RhsM_h zkq=cC1g(sjdiv>J{=1fEfBDau)^P1tPF-<*;hnd)I3Mk0c&=KIm3{B)n}@Zm9SrXn z?jQ&Y`F8(Y`3GI!MHQHKFc&C;h{t;yx-I#3KSEYqq5mMsuenJ3O$WyVz5~4o;ycq5 zy9R3pb0q|EU}0~lm+Tj<#}XN9b!C1;u^h;iPk8?M=k27875$HYsF*Q#WSGxB%Ok_W e(WFpumVHl5O59?hi(7$7fx*+&&t;ucLK6UVCGySy literal 0 HcmV?d00001 diff --git a/app/ui/contact/export_dialog.py b/app/ui/contact/export_dialog.py index d83d874..9dfaa69 100644 --- a/app/ui/contact/export_dialog.py +++ b/app/ui/contact/export_dialog.py @@ -9,7 +9,9 @@ types = { '语音': 34, '视频': 43, '表情包': 47, + '文件': 49_06, '拍一拍等系统消息': 10000 + } Stylesheet = """ QPushButton{ @@ -28,6 +30,7 @@ class ExportDialog(QDialog): if file_type == 'html': self.export_type = Output.HTML self.export_choices = {"文本": True, "图片": True, "语音": True, "视频": True, "表情包": True, + '文件': True, '拍一拍等系统消息': True} # 定义导出的数据类型,默认全部选择 elif file_type == 'csv': self.export_type = Output.CSV diff --git a/app/util/file.py b/app/util/file.py new file mode 100644 index 0000000..101c2b4 --- /dev/null +++ b/app/util/file.py @@ -0,0 +1,63 @@ +import os +import traceback +import shutil + +import requests + +from app.log import log, logger +from app.util.protocbuf.msg_pb2 import MessageBytesExtra +from ..person import MePC + +root_path = './data/files/' +if not os.path.exists('./data'): + os.mkdir('./data') +if not os.path.exists(root_path): + os.mkdir(root_path) + + +class File: + def __init__(self): + self.open_flag = False + + +def get_file(bytes_extra, thumb=False, output_path=root_path) -> str: + try: + msg_bytes = MessageBytesExtra() + msg_bytes.ParseFromString(bytes_extra) + file_original_path = '' + file_path = '' + file_name = '' + real_path = '' + if len(msg_bytes.message2) > 0: + file_field = msg_bytes.message2[-1].field2 + if file_field.find('sec_msg_node') == -1: + file_original_path = file_field + file_name = os.path.basename(file_original_path) + if file_name != '' and file_name != MePC().wxid: + file_path = os.path.join(output_path, file_name) + if os.path.exists(file_path): + print('文件' + file_path + '已存在') + return file_path + if os.path.isabs(file_original_path): + if os.path.exists(file_original_path): + real_path = file_original_path + else: # 如果没找到再判断一次是否是迁移了目录 + if file_original_path.find(r"FileStorage") != -1: + real_path = MePC().wx_dir + file_original_path[ + file_original_path.find("FileStorage") - 1:] + else: + if file_original_path.find(MePC().wxid) != -1: + real_path = MePC().wx_dir + file_original_path.replace(MePC().wxid, '') + else: + real_path = MePC().wx_dir + file_original_path + if real_path != "": + if os.path.exists(real_path): + print('开始获取文件' + real_path) + shutil.copy2(real_path, file_path) + else: + print('文件' + file_original_path + '已丢失') + file_path = '' + return file_path + except: + logger.error(traceback.format_exc()) + return "" From 65de51fb454b7ae2127b760383f1998c4dce6e6c Mon Sep 17 00:00:00 2001 From: wuyanyun Date: Sat, 23 Dec 2023 21:50:57 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0html=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=96=87=E4=BB=B6=E7=9A=84=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/DataBase/output_pc.py | 34 +++++++++++++++++ app/resources/icons/file.png | Bin 0 -> 568 bytes app/ui/contact/export_dialog.py | 2 + app/util/file.py | 63 ++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+) create mode 100644 app/resources/icons/file.png create mode 100644 app/util/file.py diff --git a/app/DataBase/output_pc.py b/app/DataBase/output_pc.py index 94b5f19..23bca94 100644 --- a/app/DataBase/output_pc.py +++ b/app/DataBase/output_pc.py @@ -18,6 +18,7 @@ import shutil from ..util.compress_content import parser_reply from ..util.emoji import get_emoji, get_emoji_path from ..util.image import get_image_path, get_image +from ..util.file import get_file os.makedirs('./data/聊天记录', exist_ok=True) @@ -359,6 +360,37 @@ class ChildThread(QThread): def wx_file(self, doc, isSend, content, status): return + def file(self, doc, message): + origin_docx_path = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}" + bytesExtra = message[10] + str_time = message[8] + is_send = message[4] + timestamp = message[5] + is_chatroom = 1 if self.contact.is_chatroom else 0 + if is_chatroom: + avatar = f"./avatar/{message[12].wxid}.png" + else: + avatar = f"./avatar/{MePC().wxid if is_send else self.contact.wxid}.png" + if is_chatroom: + if is_send: + displayname = MePC().name + else: + displayname = message[12].remark + else: + displayname = MePC().name if is_send else self.contact.remark + displayname = escape_js_and_html(displayname) + if self.output_type == Output.HTML: + link = get_file(bytesExtra, thumb=True, output_path=origin_docx_path + '/file') + file_name = '' + shutil.copy(f"{os.path.abspath('.')}/app/resources/icons/file.png", origin_docx_path + '/file/file.png') + file_path = './file/file.png' + if link != "": + file_name = os.path.basename(link) + link = './file/' + file_name + doc.write( + f'''{{ type:49, text: '{file_path}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{displayname}',link: '{link}',sub_type:6,file_name: '{file_name}'}},''' + ) + def retract_message(self, doc, isSend, content, status): return @@ -569,6 +601,8 @@ class ChildThread(QThread): self.system_msg(f, message) elif type_ == 49 and sub_type == 57 and self.message_types.get(1): self.refermsg(f, message) + elif type_ == 49 and sub_type == 6 and self.message_types.get(4906): + self.file(f, message) f.write(html_end) f.close() self.okSignal.emit(1) diff --git a/app/resources/icons/file.png b/app/resources/icons/file.png new file mode 100644 index 0000000000000000000000000000000000000000..565cf36d3ce1a2950988b296684b68e897a27199 GIT binary patch literal 568 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=oCO|{#S9F5M?jcysy3fA0|VnL zPZ!6KiaBp*`)4s3inPanI`&*%#Bs)&rQsh~Y`ystwR3a5`!8-TS-q&EgIhfAK(Dl* zY@u50lxe#%4d;GbS$W^?;dFg{V^0N!CIt?;#YV5pUwr?4eQA*9i535C1um`&TOGOh z;tbhaX59@}-j>O~`ueMOV+706O+|ao7wxQJZE$DU(7wexnxWKcZka@z?%L8*vH$%h zsc_k8&+=vXTD5nZ;j-2a0hWUo)Bf8pQEgyrJ{T;~R^IBwI7x-k#b@Q~98YHk-uA=k z$!Buy=Cdm`X>>VgK6PcikYRF5+D7nT*=}FeJBB~{8KnA;>mQr(?D~V^oiYlKBG3F0 zU&oYTG;`0DtgUG>$2Y{SSL{-9aJdi_zlXteu3!GDt69a{Z#xRG+;84?t!b-RhsM_h zkq=cC1g(sjdiv>J{=1fEfBDau)^P1tPF-<*;hnd)I3Mk0c&=KIm3{B)n}@Zm9SrXn z?jQ&Y`F8(Y`3GI!MHQHKFc&C;h{t;yx-I#3KSEYqq5mMsuenJ3O$WyVz5~4o;ycq5 zy9R3pb0q|EU}0~lm+Tj<#}XN9b!C1;u^h;iPk8?M=k27875$HYsF*Q#WSGxB%Ok_W e(WFpumVHl5O59?hi(7$7fx*+&&t;ucLK6UVCGySy literal 0 HcmV?d00001 diff --git a/app/ui/contact/export_dialog.py b/app/ui/contact/export_dialog.py index 07f51a2..b68ecf0 100644 --- a/app/ui/contact/export_dialog.py +++ b/app/ui/contact/export_dialog.py @@ -12,6 +12,7 @@ types = { '语音': 34, '视频': 43, '表情包': 47, + '文件': 49_06, '拍一拍等系统消息': 10000 } Stylesheet = """ @@ -32,6 +33,7 @@ class ExportDialog(QDialog): if file_type == 'html': self.export_type = Output.HTML self.export_choices = {"文本": True, "图片": True, "语音": False, "视频": False, "表情包": False, + '文件': True, '拍一拍等系统消息': True} # 定义导出的数据类型,默认全部选择 elif file_type == 'csv': self.export_type = Output.CSV diff --git a/app/util/file.py b/app/util/file.py new file mode 100644 index 0000000..101c2b4 --- /dev/null +++ b/app/util/file.py @@ -0,0 +1,63 @@ +import os +import traceback +import shutil + +import requests + +from app.log import log, logger +from app.util.protocbuf.msg_pb2 import MessageBytesExtra +from ..person import MePC + +root_path = './data/files/' +if not os.path.exists('./data'): + os.mkdir('./data') +if not os.path.exists(root_path): + os.mkdir(root_path) + + +class File: + def __init__(self): + self.open_flag = False + + +def get_file(bytes_extra, thumb=False, output_path=root_path) -> str: + try: + msg_bytes = MessageBytesExtra() + msg_bytes.ParseFromString(bytes_extra) + file_original_path = '' + file_path = '' + file_name = '' + real_path = '' + if len(msg_bytes.message2) > 0: + file_field = msg_bytes.message2[-1].field2 + if file_field.find('sec_msg_node') == -1: + file_original_path = file_field + file_name = os.path.basename(file_original_path) + if file_name != '' and file_name != MePC().wxid: + file_path = os.path.join(output_path, file_name) + if os.path.exists(file_path): + print('文件' + file_path + '已存在') + return file_path + if os.path.isabs(file_original_path): + if os.path.exists(file_original_path): + real_path = file_original_path + else: # 如果没找到再判断一次是否是迁移了目录 + if file_original_path.find(r"FileStorage") != -1: + real_path = MePC().wx_dir + file_original_path[ + file_original_path.find("FileStorage") - 1:] + else: + if file_original_path.find(MePC().wxid) != -1: + real_path = MePC().wx_dir + file_original_path.replace(MePC().wxid, '') + else: + real_path = MePC().wx_dir + file_original_path + if real_path != "": + if os.path.exists(real_path): + print('开始获取文件' + real_path) + shutil.copy2(real_path, file_path) + else: + print('文件' + file_original_path + '已丢失') + file_path = '' + return file_path + except: + logger.error(traceback.format_exc()) + return "" From fff76ae457ac3efeecf6bd998a97792296ea5a75 Mon Sep 17 00:00:00 2001 From: wuyanyun Date: Sat, 23 Dec 2023 23:07:51 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E5=90=88=E5=B9=B6html=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E7=9A=84=E6=A8=A1=E6=9D=BF=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/resources/template.html | 55 ++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/app/resources/template.html b/app/resources/template.html index 931b25c..5f37bb8 100644 --- a/app/resources/template.html +++ b/app/resources/template.html @@ -161,7 +161,7 @@ body{ color: darkgray; } -.chat-image img{ +.chat-image img,.chat-file img{ margin-right: 18px; margin-left: 18px; max-width: 300px; @@ -184,6 +184,29 @@ audio{ margin-left: 9px; margin-right: 9px; } + +.chat-file { + width: 300px; + background-color: #fff; + margin-right: 20px; +} +.chat-file a ,.chat-file div{ + display: flex; + color: #000; + outline: none; + text-decoration: none; + margin: 0 20px 20px 20px; +} +.chat-file div{ + margin: 20px; +} +.chat-file a span ,.chat-file div span{ + /* flex-grow: 1; */ + width: 200px; +} +.chat-file a img,.chat-file div img{ + width: 100px; +} .input-area{ border-top:0.5px solid #e0e0e0; height: 150px; @@ -361,9 +384,9 @@ input { // 计算当前页应该显示的元素范围 const startIndex = (page - 1) * itemsPerPage; const endIndex = startIndex + itemsPerPage; - + function replaceEmoji(text){ - + // 定义替换规则,可以根据需要添加更多规则 var replacementRules = [ { pattern: /\[微笑\]/g, replacement: '' }, @@ -534,8 +557,19 @@ input { messageAudioTag.innerHTML = ``; return messageAudioTag; } + function messageFileBox(message) { + const messageFileTag = document.createElement('div'); + messageFileTag.className = `chat-file`; + if (message.link !== ''){ + messageFileTag.innerHTML = ` + ${message.file_name}` + }else{ + messageFileTag.innerHTML = `
文件已丢失
`; + } + return messageFileTag; + } - // 从数据列表中取出对应范围的元素并添加到容器中 + // 从数据列表中取出对应范围的元素并添加到容器中 for (let i = startIndex; i < endIndex && i < chatMessages.length; i++) { const message = chatMessages[i]; if (i == startIndex) { // 判断一下在页面顶部多加一个时间 @@ -619,6 +653,19 @@ input { messageContent.appendChild(messageElementReferText(message, side)); } + // 整合 + messageElement.className = `item item-${side}`; + messageElement.appendChild(message.is_send ? messageContent : avatarTag); + messageElement.appendChild(message.is_send ? avatarTag : messageContent); + } + if (message.sub_type == 6) { + // displayname 和 file + messageContent.className = `content-wrapper content-wrapper-${side}`; + if (message.is_chatroom && !message.is_send) { + messageContent.appendChild(displayNameBox(message)); + } + messageContent.appendChild(messageFileBox(message)); + // 整合 messageElement.className = `item item-${side}`; messageElement.appendChild(message.is_send ? messageContent : avatarTag); From 8d904ee7b60d089ac1cffc1cb63c3b8af26d90e1 Mon Sep 17 00:00:00 2001 From: wuyanyun Date: Sat, 23 Dec 2023 23:19:12 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E8=B0=83=E6=95=B4=E5=86=99=E9=94=99?= =?UTF-8?q?=E7=9A=84=E5=AF=BC=E5=87=BA=E7=B1=BB=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/ui/contact/export_dialog.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/ui/contact/export_dialog.py b/app/ui/contact/export_dialog.py index b68ecf0..2845a6e 100644 --- a/app/ui/contact/export_dialog.py +++ b/app/ui/contact/export_dialog.py @@ -12,7 +12,7 @@ types = { '语音': 34, '视频': 43, '表情包': 47, - '文件': 49_06, + '文件': 4906, '拍一拍等系统消息': 10000 } Stylesheet = """