From 240e296bd39bc28affb7a55f457bcd4de4565bd2 Mon Sep 17 00:00:00 2001 From: shuaikangzhou <863909694@qq.com> Date: Tue, 2 Jan 2024 00:39:45 +0800 Subject: [PATCH] =?UTF-8?q?html=E6=94=AF=E6=8C=81=E5=AF=BC=E5=87=BA?= =?UTF-8?q?=E5=88=86=E4=BA=AB=E7=9A=84=E5=8D=A1=E7=89=87=E9=93=BE=E6=8E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/DataBase/msg.py | 32 ++++++++++++- app/DataBase/output_pc.py | 41 +++++++++++++++- app/resources/data/pause.png | Bin 0 -> 386 bytes app/resources/data/play.png | Bin 0 -> 716 bytes app/resources/data/template.html | 77 +++++++++++++++++++++++++++++-- app/ui/contact/export_dialog.py | 3 +- app/util/compress_content.py | 43 +++++++++++++++-- 7 files changed, 187 insertions(+), 9 deletions(-) create mode 100644 app/resources/data/pause.png create mode 100644 app/resources/data/play.png diff --git a/app/DataBase/msg.py b/app/DataBase/msg.py index 70b3b99..d1a7c44 100644 --- a/app/DataBase/msg.py +++ b/app/DataBase/msg.py @@ -4,6 +4,7 @@ import sqlite3 import threading import traceback +from app.DataBase.hard_link import parseBytes from app.log import logger from app.util.compress_content import parser_reply from app.util.protocbuf.msg_pb2 import MessageBytesExtra @@ -629,4 +630,33 @@ if __name__ == '__main__': msg.init_database() wxid = 'wxid_0o18ef858vnu22' wxid = '24521163022@chatroom' - print(msg.get_messages(wxid)[0]) + wxid = 'wxid_vtz9jk9ulzjt22' # si + print() + from app.util import compress_content + import xml.etree.ElementTree as ET + msgs = msg.get_messages(wxid) + + for msg in msgs: + if msg[2]==49 and msg[3]==5: + xml = compress_content.decompress_CompressContent(msg[11]) + root = ET.XML(xml) + appmsg = root.find('appmsg') + title = appmsg.find('title').text + des = appmsg.find('des').text + url = appmsg.find('url').text + appinfo = root.find('appinfo') + show_display_name = appmsg.find('sourcedisplayname') + if show_display_name is not None: + show_display_name = show_display_name.text + else: + show_display_name = appinfo.find('appname').text + print(title, des, url, show_display_name) + bytesDict = parseBytes(msg[10]) + for msginfo in bytesDict[3]: + print(msginfo) + if msginfo[1][1][1] == 3: + thumb = msginfo[1][2][1] + print(thumb) + if msginfo[1][1][1] == 4: + app_logo = msginfo[1][2][1] + print('logo',app_logo) \ No newline at end of file diff --git a/app/DataBase/output_pc.py b/app/DataBase/output_pc.py index 41a8bc8..87f8d0c 100644 --- a/app/DataBase/output_pc.py +++ b/app/DataBase/output_pc.py @@ -20,7 +20,7 @@ from ..DataBase import media_msg_db, hard_link_db, micro_msg_db, msg_db from ..log import logger from ..person import Me from ..util import path -from ..util.compress_content import parser_reply, music_share +from ..util.compress_content import parser_reply, music_share, share_card from ..util.emoji import get_emoji_url from ..util.file import get_file from ..util.music import get_music_path @@ -123,12 +123,14 @@ class Output(QThread): @return: """ return + def output_emoji(self): """ 导出全部表情包 @return: """ return + def to_csv_all(self): """ 导出全部聊天记录到CSV @@ -658,6 +660,41 @@ class ChildThread(QThread): artist:'{content.get('artist')}', website_name:'{content.get('website_name')}'}},''' ) + def share_card(self, doc, message): + origin_docx_path = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}" + is_send = message[4] + timestamp = message[5] + bytesExtra = message[10] + compress_content_ = message[11] + card_data = share_card(bytesExtra, compress_content_) + is_chatroom = 1 if self.contact.is_chatroom else 0 + avatar = self.get_avatar_path(is_send, message) + display_name = self.get_display_name(is_send, message) + thumbnail = '' + if card_data.get('thumbnail'): + thumbnail = os.path.join(Me().wx_dir, card_data.get('thumbnail')) + if os.path.exists(thumbnail): + shutil.copy(thumbnail, os.path.join(origin_docx_path, 'image', os.path.basename(thumbnail))) + thumbnail = './image/' + os.path.basename(thumbnail) + else: + thumbnail = '' + app_logo = '' + if card_data.get('app_logo'): + app_logo = os.path.join(Me().wx_dir, card_data.get('app_logo')) + if os.path.exists(app_logo): + shutil.copy(app_logo, os.path.join(origin_docx_path, 'image', os.path.basename(app_logo))) + app_logo = './image/' + os.path.basename(app_logo) + else: + app_logo = '' + if self.output_type == Output.HTML: + doc.write( + f'''{{ type:49,sub_type:5, text:'',is_send:{is_send},avatar_path:'{avatar}',url:'{card_data.get('url')}', + timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}',title:'{card_data.get('title')}', + description:'{card_data.get('description')}',thumbnail:'{thumbnail}',app_logo:'{app_logo}', + app_name:'{card_data.get('app_name')}' + }},\n''' + ) + def to_csv(self): origin_docx_path = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}" os.makedirs(origin_docx_path, exist_ok=True) @@ -732,6 +769,8 @@ class ChildThread(QThread): self.file(f, message) elif type_ == 49 and sub_type == 3 and self.message_types.get(4903): self.music_share(f, message) + elif type_ == 49 and sub_type == 5 and self.message_types.get(4905): + self.share_card(f, message) f.write(html_end) f.close() self.okSignal.emit(1) diff --git a/app/resources/data/pause.png b/app/resources/data/pause.png new file mode 100644 index 0000000000000000000000000000000000000000..cdf0cf46c15b79ea00d7dbe024aef64a565c8898 GIT binary patch literal 386 zcmV-|0e$|7P)Px$JV``BR5(wiQ$cQnFc6$|6qHW{Ij6!aN?uZUNs^a@z9jS&kQ0=Aq9T&FS_@Oh zLNrxlTm(Qe|1{Okj90Gbe?|Fs2a-L~MUh@#JmII?WTRR0-cE|YsO zk~7Dmk@O}bV=l~Gq_=xHS$Q01E^eF)&7zfEMK4T02?Rd9WFlnwkVcyez0t~;gP%Yd gGZTG4sWJ@NH)YxMaK?Ep)&Kwi07*qoM6N<$f@j&Be*gdg literal 0 HcmV?d00001 diff --git a/app/resources/data/play.png b/app/resources/data/play.png new file mode 100644 index 0000000000000000000000000000000000000000..09f6fe269bc75ecbcb85b65ce841ab8cb1d21fd4 GIT binary patch literal 716 zcmV;-0yF)IP)Px%hDk(0R9HvFS8Zs|=B1bCp|4&Xl^Hc(Z~qWP>ZNvasY zel~d(0M7uRG65O{__H`1UrT^w0qpPZUW5=^c9;&J|5Mhqgiyo=0%&H2RRkmn;O}g* zWrsG1xLJIhcCpNKc>t7X+cgNW)EEIaDK?@2-tX_;>>-G&RNxSxp=4{r#{6)64DAa7 zV8I)da&@V&Odw)Jblsa~1z`8m@U0as=RgHlfYx8o@1y zvydCV6b=AeH^h4Wae4~YR!x=cx-}vytg1~ZL#C|0C>;PL*?XNqPPXf$Bp`iDM%G?U zmxCT07JL9m@6Qj%Xm?luh_&ew0Lb?IAjn<|Oj|aP5noKLCNTqm1O-)9lPwDX+iocr zv}IsoJOk*hcAYa04#0SW&aQ2@4kLY*PFak`cnk|5ba1GgseJ>B#0ELyNbw<-uM40T z8qJNM@gxC+=qY6&Qi73#CQD!)FyR3N=qDl!mG3OV-nXeuMz!NiMA|Sr_2>-dGml1~ zt`7_oFlEEulp}+$v8ssfv=Om;1@{@(iW8pg441y;XkHXg$|5V~<1^<6Dckj*@d@j@ zY-$|;=q|t*Gc&-kVDmia`K-nFVt%N3Q7=UZ7*+CN-+cxu9aC$E~_00X> z88ii=^Gzu?-167*@%KO@0BZX&qSFxiTY-V4zeUVt0nka74aDDU;>Dy;6gVlu%*{qY y+b+$=2<_Eu(2{bOGbI1)A_huT|DAm|2k-^V*a|;lF?f{#0000`; return messageAudioTag; } + function messageCard(message) { + const messageTag = document.createElement('div'); + messageTag.className = `card`; + messageTag.innerHTML = `

${message.title}

${message.description}

Thumbnail
`; + return messageTag; + } function messageFileBox(message) { const messageFileTag = document.createElement('div'); messageFileTag.className = `chat-file`; @@ -835,7 +893,7 @@ input { messageElement.appendChild(message.is_send ? messageContent : avatarTag); messageElement.appendChild(message.is_send ? avatarTag : messageContent); } - if (message.sub_type == 6) { + else if (message.sub_type == 6) { // displayname 和 file messageContent.className = `content-wrapper content-wrapper-${side}`; if (message.is_chatroom && !message.is_send) { @@ -848,7 +906,7 @@ input { messageElement.appendChild(message.is_send ? messageContent : avatarTag); messageElement.appendChild(message.is_send ? avatarTag : messageContent); } - if (message.sub_type == 3) { + else if (message.sub_type == 3) { // displayname 和 file messageContent.className = `content-wrapper content-wrapper-${side}`; if (message.is_chatroom && !message.is_send) { @@ -861,6 +919,19 @@ input { messageElement.appendChild(message.is_send ? messageContent : avatarTag); messageElement.appendChild(message.is_send ? avatarTag : messageContent); } + else if(message.sub_type == 5){ + // displayname 和 file + messageContent.className = `content-wrapper content-wrapper-${side}`; + if (message.is_chatroom && !message.is_send) { + messageContent.appendChild(displayNameBox(message)); + } + messageContent.appendChild(messageCard(message)); + + // 整合 + messageElement.className = `item item-${side}`; + messageElement.appendChild(message.is_send ? messageContent : avatarTag); + messageElement.appendChild(message.is_send ? avatarTag : messageContent); + } } else if (message.type == 34) { diff --git a/app/ui/contact/export_dialog.py b/app/ui/contact/export_dialog.py index 2a89f53..1bd92f9 100644 --- a/app/ui/contact/export_dialog.py +++ b/app/ui/contact/export_dialog.py @@ -14,6 +14,7 @@ types = { '表情包': 47, '音乐与音频': 4903, '文件': 4906, + '分享卡片':4905, '拍一拍等系统消息': 10000 } Stylesheet = """ @@ -34,7 +35,7 @@ class ExportDialog(QDialog): if file_type == 'html': self.export_type = Output.HTML self.export_choices = {"文本": True, "图片": True, "语音": False, "视频": False, "表情包": False, - '音乐与音频': False,'文件': False, + '音乐与音频': False,'分享卡片':False,'文件': False, '拍一拍等系统消息': True} # 定义导出的数据类型,默认全部选择 elif file_type == 'csv': self.export_type = Output.CSV diff --git a/app/util/compress_content.py b/app/util/compress_content.py index 8450e81..245018b 100644 --- a/app/util/compress_content.py +++ b/app/util/compress_content.py @@ -7,6 +7,7 @@ import requests from urllib.parse import urlparse from bs4 import BeautifulSoup +from app.DataBase.hard_link import parseBytes def decompress_CompressContent(data): @@ -20,13 +21,15 @@ def decompress_CompressContent(data): try: dst = lz4.block.decompress(data, uncompressed_size=len(data) << 10) decoded_string = dst.decode().replace('\x00', '') # Remove any null characters - except : + except: print("Decompression failed: potentially corrupt input or insufficient buffer size.") return '' return decoded_string def escape_js_and_html(input_str): + if not input_str: + return '' # 转义HTML特殊字符 html_escaped = html.escape(input_str, quote=False) @@ -125,6 +128,40 @@ def music_share(data: bytes): } +def share_card(bytesExtra, compress_content_): + xml = decompress_CompressContent(compress_content_) + root = ET.XML(xml) + appmsg = root.find('appmsg') + title = appmsg.find('title').text + des = appmsg.find('des').text + url = appmsg.find('url').text + appinfo = root.find('appinfo') + show_display_name = appmsg.find('sourcedisplayname') + if show_display_name is not None: + show_display_name = show_display_name.text + else: + if appinfo is not None: + show_display_name = appinfo.find('appname').text + bytesDict = parseBytes(bytesExtra) + app_logo = '' + thumbnail = '' + for msginfo in bytesDict[3]: + if msginfo[1][1][1] == 3: + thumbnail = msginfo[1][2][1] + thumbnail = "\\".join(thumbnail.split('\\')[1:]) + if msginfo[1][1][1] == 4: + app_logo = msginfo[1][2][1] + app_logo = "\\".join(app_logo.split('\\')[1:]) + return { + 'title': escape_js_and_html(title), + 'description': escape_js_and_html(des), + 'url': url, + 'app_name': escape_js_and_html(show_display_name), + 'thumbnail': thumbnail, + 'app_logo': app_logo + } + + def get_website_name(url): parsed_url = urlparse(url) domain = f"{parsed_url.scheme}://{parsed_url.netloc}" @@ -146,7 +183,7 @@ def get_website_name(url): website_name = soup.title.string.strip() index = website_name.find("-") if index != -1: # 如果找到了 "-" - website_name = website_name[index+1:].strip() + website_name = website_name[index + 1:].strip() except Exception as e: print(f"Get Website Info Error: {e}") return website_name @@ -162,7 +199,7 @@ def get_audio_url(url): elif response.status_code == 200: print('音乐文件已失效,url:' + url) else: - print('音乐文件地址获取失败,url:' + url +',状态码' + str(response.status_code)) + print('音乐文件地址获取失败,url:' + url + ',状态码' + str(response.status_code)) except Exception as e: print(f"Get Audio Url Error: {e}") return path