完成解析转账和音视频通话类型信息的两个后端接口

This commit is contained in:
STDquantum 2024-01-18 13:51:14 +08:00
parent abf3d4540f
commit a977359d87
10 changed files with 389 additions and 219 deletions

View File

@ -22,7 +22,7 @@ class CSVExporter(ExporterBase):
# 写入数据
# writer.writerows(messages)
for msg in messages:
other_data = [msg[12].remark, msg[12].nickName, msg[12].wxid] if self.contact.is_chatroom else []
other_data = [msg[13].remark, msg[13].nickName, msg[13].wxid] if self.contact.is_chatroom else []
writer.writerow([*msg[:9], *other_data])
print(f"【完成导出 CSV {self.contact.remark}")
self.okSignal.emit(1)

View File

@ -320,8 +320,8 @@ class DocxExporter(ExporterBase):
if message[4]: # is_send
continue
try:
chatroom_avatar_path = f"{origin_docx_path}/avatar/{message[12].wxid}.png"
message[12].save_avatar(chatroom_avatar_path)
chatroom_avatar_path = f"{origin_docx_path}/avatar/{message[13].wxid}.png"
message[13].save_avatar(chatroom_avatar_path)
except:
print(message)
pass

View File

@ -11,19 +11,26 @@ from app.DataBase.output import ExporterBase, escape_js_and_html
from app.log import logger
from app.person import Me
from app.util import path
from app.util.compress_content import parser_reply, share_card, music_share, file
from app.util.compress_content import (
parser_reply,
share_card,
music_share,
file,
transfer_decompress,
call_decompress,
)
from app.util.emoji import get_emoji_url
from app.util.image import get_image_path, get_image
from app.util.music import get_music_path
icon_files = {
'./icon/word.png': ['doc', 'docx'],
'./icon/excel.png': ['xls', 'xlsx'],
'./icon/csv.png': ['csv'],
'./icon/txt.png': ['txt'],
'./icon/zip.png': ['zip', '7z', 'rar'],
'./icon/ppt.png': ['ppt', 'pptx'],
'./icon/pdf.png': ['pdf'],
"./icon/word.png": ["doc", "docx"],
"./icon/excel.png": ["xls", "xlsx"],
"./icon/csv.png": ["csv"],
"./icon/txt.png": ["txt"],
"./icon/zip.png": ["zip", "7z", "rar"],
"./icon/ppt.png": ["ppt", "pptx"],
"./icon/pdf.png": ["pdf"],
}
@ -40,7 +47,7 @@ class HtmlExporter(ExporterBase):
avatar = self.get_avatar_path(is_send, message)
str_content = escape_js_and_html(str_content)
doc.write(
f'''{{ type:{1}, text: '{str_content}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},'''
f"""{{ type:{1}, text: '{str_content}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},"""
)
def image(self, doc, message):
@ -57,13 +64,17 @@ class HtmlExporter(ExporterBase):
str_content = escape_js_and_html(str_content)
image_path = hard_link_db.get_image(str_content, BytesExtra, thumb=False)
if not os.path.exists(os.path.join(Me().wx_dir, image_path)):
image_thumb_path = hard_link_db.get_image(str_content, BytesExtra, thumb=True)
image_thumb_path = hard_link_db.get_image(
str_content, BytesExtra, thumb=True
)
if not os.path.exists(os.path.join(Me().wx_dir, image_thumb_path)):
return
image_path = image_thumb_path
image_path = get_image_path(image_path, base_path=f'/data/聊天记录/{self.contact.remark}/image')
image_path = get_image_path(
image_path, base_path=f"/data/聊天记录/{self.contact.remark}/image"
)
doc.write(
f'''{{ type:{type_}, text: '{image_path}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},'''
f"""{{ type:{type_}, text: '{image_path}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},"""
)
def audio(self, doc, message):
@ -77,7 +88,9 @@ class HtmlExporter(ExporterBase):
avatar = self.get_avatar_path(is_send, message)
display_name = self.get_display_name(is_send, message)
try:
audio_path = media_msg_db.get_audio_path(msgSvrId, output_path=origin_docx_path + "/voice")
audio_path = media_msg_db.get_audio_path(
msgSvrId, output_path=origin_docx_path + "/voice"
)
audio_path = "./voice/" + os.path.basename(audio_path)
except:
logger.error(traceback.format_exc())
@ -86,7 +99,7 @@ class HtmlExporter(ExporterBase):
if voice_to_text and voice_to_text != "":
voice_to_text = escape_js_and_html(voice_to_text)
doc.write(
f'''{{ type:34, text:'{audio_path}',is_send:{is_send},avatar_path:'{avatar}',voice_to_text:'{voice_to_text}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},'''
f"""{{ type:34, text:'{audio_path}',is_send:{is_send},avatar_path:'{avatar}',voice_to_text:'{voice_to_text}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},"""
)
def emoji(self, doc, message):
@ -99,7 +112,7 @@ class HtmlExporter(ExporterBase):
display_name = self.get_display_name(is_send, message)
emoji_path = get_emoji_url(str_content, thumb=True)
doc.write(
f'''{{ type:{3}, text: '{emoji_path}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},'''
f"""{{ type:{3}, text: '{emoji_path}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},"""
)
def file(self, doc, message):
@ -112,25 +125,27 @@ class HtmlExporter(ExporterBase):
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)
file_info = file(bytesExtra, compress_content, output_path=origin_docx_path + '/file')
if file_info.get('is_error') == False:
file_info = file(
bytesExtra, compress_content, output_path=origin_docx_path + "/file"
)
if file_info.get("is_error") == False:
icon_path = None
for icon, extensions in icon_files.items():
if file_info.get('file_ext') in extensions:
if file_info.get("file_ext") in extensions:
icon_path = icon
break
# 如果没有与文件后缀匹配的图标,则使用默认图标
if icon_path is None:
default_icon = './icon/file.png'
default_icon = "./icon/file.png"
icon_path = default_icon
file_path = file_info.get('file_path')
file_path = file_info.get("file_path")
if file_path != "":
file_path = './file/' + file_info.get('file_name')
file_path = "./file/" + file_info.get("file_name")
doc.write(
f'''{{ type:49, text: '{file_path}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp}
f"""{{ type:49, text: '{file_path}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp}
,is_chatroom:{is_chatroom},displayname:'{display_name}',icon_path: '{icon_path}'
,sub_type:6,file_name: '{file_info.get('file_name')}',file_size: '{file_info.get('file_len')}'
,app_name: '{file_info.get('app_name')}'}},'''
,app_name: '{file_info.get('app_name')}'}},"""
)
def refermsg(self, doc, message):
@ -143,20 +158,20 @@ class HtmlExporter(ExporterBase):
str_time = message[8]
is_send = message[4]
content = parser_reply(message[11])
refer_msg = content.get('refer')
refer_msg = content.get("refer")
timestamp = message[5]
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)
contentText = escape_js_and_html(content.get('title'))
contentText = escape_js_and_html(content.get("title"))
if refer_msg:
referText = f"{escape_js_and_html(refer_msg.get('displayname'))}{escape_js_and_html(refer_msg.get('content'))}"
doc.write(
f'''{{ type:49, text: '{contentText}',is_send:{is_send},sub_type:{content.get('type')},refer_text: '{referText}',avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},'''
f"""{{ type:49, text: '{contentText}',is_send:{is_send},sub_type:{content.get('type')},refer_text: '{referText}',avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},"""
)
else:
doc.write(
f'''{{ type:49, text: '{contentText}',is_send:{is_send},sub_type:{content.get('type')},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},'''
f"""{{ type:49, text: '{contentText}',is_send:{is_send},sub_type:{content.get('type')},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},"""
)
def system_msg(self, doc, message):
@ -166,14 +181,15 @@ class HtmlExporter(ExporterBase):
timestamp = message[5]
is_chatroom = 1 if self.contact.is_chatroom else 0
str_content = str_content.replace('<![CDATA[', "").replace(
' <a href="weixin://revoke_edit_click">重新编辑</a>]]>', "")
res = findall('(</{0,1}(img|revo|_wc_cus|a).*?>)', str_content)
str_content = str_content.replace("<![CDATA[", "").replace(
' <a href="weixin://revoke_edit_click">重新编辑</a>]]>', ""
)
res = findall("(</{0,1}(img|revo|_wc_cus|a).*?>)", str_content)
for xmlstr, b in res:
str_content = str_content.replace(xmlstr, "")
str_content = escape_js_and_html(str_content)
doc.write(
f'''{{ type:0, text: '{str_content}',is_send:{is_send},avatar_path:'',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:''}},'''
f"""{{ type:0, text: '{str_content}',is_send:{is_send},avatar_path:'',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:''}},"""
)
def video(self, doc, message):
@ -190,30 +206,32 @@ class HtmlExporter(ExporterBase):
video_path = hard_link_db.get_video(str_content, BytesExtra, thumb=False)
image_path = hard_link_db.get_video(str_content, BytesExtra, thumb=True)
if video_path is None and image_path is not None:
image_path = path.get_relative_path(image_path, base_path=f'/data/聊天记录/{self.contact.remark}/image')
image_path = path.get_relative_path(
image_path, base_path=f"/data/聊天记录/{self.contact.remark}/image"
)
try:
# todo 网络图片问题
print(origin_docx_path + image_path[1:])
os.utime(origin_docx_path + image_path[1:], (timestamp, timestamp))
doc.write(
f'''{{ type:3, text: '{image_path}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},'''
f"""{{ type:3, text: '{image_path}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},"""
)
except:
doc.write(
f'''{{ type:1, text: '视频丢失',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},'''
f"""{{ type:1, text: '视频丢失',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},"""
)
return
if video_path is None and image_path is None:
return
video_path = f'{Me().wx_dir}/{video_path}'
video_path = f"{Me().wx_dir}/{video_path}"
if os.path.exists(video_path):
new_path = origin_docx_path + '/video/' + os.path.basename(video_path)
new_path = origin_docx_path + "/video/" + os.path.basename(video_path)
if not os.path.exists(new_path):
shutil.copy(video_path, os.path.join(origin_docx_path, 'video'))
shutil.copy(video_path, os.path.join(origin_docx_path, "video"))
os.utime(new_path, (timestamp, timestamp))
video_path = f'./video/{os.path.basename(video_path)}'
video_path = f"./video/{os.path.basename(video_path)}"
doc.write(
f'''{{ type:{type_}, text: '{video_path}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},'''
f"""{{ type:{type_}, text: '{video_path}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}'}},"""
)
def music_share(self, doc, message):
@ -221,21 +239,24 @@ class HtmlExporter(ExporterBase):
is_send = message[4]
timestamp = message[5]
content = music_share(message[11])
music_path = ''
if content.get('is_error') == False:
if content.get('audio_url') != '':
music_path = get_music_path(content.get('audio_url'), content.get('title'),
output_path=origin_docx_path + '/music')
if music_path != '':
music_path = f'./music/{os.path.basename(music_path)}'
music_path = music_path.replace('\\', '/')
music_path = ""
if content.get("is_error") == False:
if content.get("audio_url") != "":
music_path = get_music_path(
content.get("audio_url"),
content.get("title"),
output_path=origin_docx_path + "/music",
)
if music_path != "":
music_path = f"./music/{os.path.basename(music_path)}"
music_path = music_path.replace("\\", "/")
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)
doc.write(
f'''{{ type:49, text:'{music_path}',is_send:{is_send},avatar_path:'{avatar}',link_url:'{content.get('link_url')}',
f"""{{ type:49, text:'{music_path}',is_send:{is_send},avatar_path:'{avatar}',link_url:'{content.get('link_url')}',
timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}',sub_type:3,title:'{content.get('title')}',
artist:'{content.get('artist')}', website_name:'{content.get('website_name')}'}},'''
artist:'{content.get('artist')}', website_name:'{content.get('website_name')}'}},"""
)
def share_card(self, doc, message):
@ -248,53 +269,116 @@ class HtmlExporter(ExporterBase):
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'))
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)
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'))
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)
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 = card_data.get('app_logo')
app_logo = card_data.get("app_logo")
doc.write(
f'''{{ type:49,sub_type:5, text:'',is_send:{is_send},avatar_path:'{avatar}',url:'{card_data.get('url')}',
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'''
}},\n"""
)
def transfer(self, doc, message):
is_send = message[4]
timestamp = message[5]
compress_content_ = message[11]
open("test.bin", "wb").write(compress_content_)
transfer_detail = transfer_decompress(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)
text_info_map = {
1: transfer_detail["pay_memo"] or "转账",
3: "已收款",
4: "已退还",
}
doc.write(
f"""{{ type:49, sub_type:2000,
text:'{text_info_map[transfer_detail["paysubtype"]]}',
is_send:{is_send},avatar_path:'{avatar}',
timestamp:{timestamp},is_chatroom:{is_chatroom},
displayname:'{display_name}',paysubtype:{transfer_detail["paysubtype"]},
pay_memo:'{transfer_detail["pay_memo"]}',feedesc:'{transfer_detail["feedesc"]}',
}},\n"""
)
def call(self, doc, message):
is_send = message[4]
timestamp = message[5]
str_content = message[7]
bytes_extra = message[10]
display_content = message[12]
call_detail = call_decompress(
is_send, bytes_extra, display_content, str_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)
doc.write(
f"""{{ type:50, text:'{call_detail["display_content"]}',
call_type:{call_detail["call_type"]},avatar_path:'{avatar}',
timestamp:{timestamp},is_chatroom:{is_chatroom},
displayname:'{display_name}',
}},\n"""
)
def export(self):
print(f"【开始导出 HTML {self.contact.remark}")
messages = msg_db.get_messages(self.contact.wxid, time_range=self.time_range)
filename = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}/{self.contact.remark}.html"
file_path = './app/resources/data/template.html'
file_path = "./app/resources/data/template.html"
if not os.path.exists(file_path):
resource_dir = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__)))
file_path = os.path.join(resource_dir, 'app', 'resources', 'data', 'template.html')
resource_dir = getattr(
sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__))
)
file_path = os.path.join(
resource_dir, "app", "resources", "data", "template.html"
)
with open(file_path, "r", encoding="utf-8") as f:
content = f.read()
html_head, html_end = content.split('/*注意看这是分割线*/')
f = open(filename, 'w', encoding='utf-8')
html_head = html_head.replace("<title>出错了</title>", f"<title>{self.contact.remark}</title>")
html_head = html_head.replace("<p id=\"title\">出错了</p>", f"<p id=\"title\">{self.contact.remark}</p>")
html_head, html_end = content.split("/*注意看这是分割线*/")
f = open(filename, "w", encoding="utf-8")
html_head = html_head.replace(
"<title>出错了</title>", f"<title>{self.contact.remark}</title>"
)
html_head = html_head.replace(
'<p id="title">出错了</p>', f'<p id="title">{self.contact.remark}</p>'
)
f.write(html_head)
self.rangeSignal.emit(len(messages))
for index, message in enumerate(messages):
type_ = message[2]
sub_type = message[3]
timestamp = message[5]
if (type_ == 3 and self.message_types.get(3)) or (type_ == 34 and self.message_types.get(34)) or (
type_ == 47 and self.message_types.get(47)):
if (
(type_ == 3 and self.message_types.get(3))
or (type_ == 34 and self.message_types.get(34))
or (type_ == 47 and self.message_types.get(47))
):
pass
else:
self.progressSignal.emit(1)
@ -319,6 +403,10 @@ class HtmlExporter(ExporterBase):
self.music_share(f, message)
elif type_ == 49 and sub_type == 5 and self.message_types.get(4905):
self.share_card(f, message)
elif type_ == 49 and sub_type == 2000 and self.message_types.get(492000):
self.transfer(f, message)
elif type_ == 50 and self.message_types.get(50):
self.call(f, message)
if index % 2000 == 0:
print(f"【导出 HTML {self.contact.remark}{index}/{len(messages)}")
f.write(html_end)
@ -333,7 +421,7 @@ class HtmlExporter(ExporterBase):
@return:
"""
self.num += 1
print('子线程完成', self.num, '/', self.total_num)
print("子线程完成", self.num, "/", self.total_num)
if self.num == self.total_num:
# 所有子线程都完成之后就发送完成信号
self.okSignal.emit(1)
@ -343,6 +431,7 @@ class OutputMedia(QThread):
"""
导出语音消息
"""
okSingal = pyqtSignal(int)
progressSignal = pyqtSignal(int)
@ -357,7 +446,9 @@ class OutputMedia(QThread):
is_send = message[4]
msgSvrId = message[9]
try:
audio_path = media_msg_db.get_audio(msgSvrId, output_path=origin_docx_path + "/voice")
audio_path = media_msg_db.get_audio(
msgSvrId, output_path=origin_docx_path + "/voice"
)
except:
logger.error(traceback.format_exc())
finally:
@ -369,6 +460,7 @@ class OutputEmoji(QThread):
"""
导出表情包
"""
okSingal = pyqtSignal(int)
progressSignal = pyqtSignal(int)
@ -395,6 +487,7 @@ class OutputImage(QThread):
"""
导出图片
"""
okSingal = pyqtSignal(int)
progressSignal = pyqtSignal(int)
@ -407,10 +500,10 @@ class OutputImage(QThread):
def count1(self, num):
self.num += 1
print('图片导出完成一个')
print("图片导出完成一个")
if self.num == self.child_thread_num:
self.okSingal.emit(47)
print('图片导出完成')
print("图片导出完成")
def run(self):
origin_docx_path = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}"
@ -420,13 +513,19 @@ class OutputImage(QThread):
BytesExtra = message[10]
timestamp = message[5]
try:
image_path = hard_link_db.get_image(str_content, BytesExtra, thumb=False)
image_path = hard_link_db.get_image(
str_content, BytesExtra, thumb=False
)
if not os.path.exists(os.path.join(Me().wx_dir, image_path)):
image_thumb_path = hard_link_db.get_image(str_content, BytesExtra, thumb=True)
image_thumb_path = hard_link_db.get_image(
str_content, BytesExtra, thumb=True
)
if not os.path.exists(os.path.join(Me().wx_dir, image_thumb_path)):
continue
image_path = image_thumb_path
image_path = get_image(image_path, base_path=f'/data/聊天记录/{self.contact.remark}/image')
image_path = get_image(
image_path, base_path=f"/data/聊天记录/{self.contact.remark}/image"
)
try:
os.utime(origin_docx_path + image_path[1:], (timestamp, timestamp))
except:
@ -464,13 +563,19 @@ class OutputImageChild(QThread):
BytesExtra = message[10]
timestamp = message[5]
try:
image_path = hard_link_db.get_image(str_content, BytesExtra, thumb=False)
image_path = hard_link_db.get_image(
str_content, BytesExtra, thumb=False
)
if not os.path.exists(os.path.join(Me().wx_dir, image_path)):
image_thumb_path = hard_link_db.get_image(str_content, BytesExtra, thumb=True)
image_thumb_path = hard_link_db.get_image(
str_content, BytesExtra, thumb=True
)
if not os.path.exists(os.path.join(Me().wx_dir, image_thumb_path)):
continue
image_path = image_thumb_path
image_path = get_image(image_path, base_path=f'/data/聊天记录/{self.contact.remark}/image')
image_path = get_image(
image_path, base_path=f"/data/聊天记录/{self.contact.remark}/image"
)
try:
os.utime(origin_docx_path + image_path[1:], (timestamp, timestamp))
except:
@ -480,4 +585,4 @@ class OutputImageChild(QThread):
finally:
self.progressSignal.emit(1)
self.okSingal.emit(47)
print('图片子线程完成')
print("图片子线程完成")

View File

@ -35,7 +35,8 @@ def parser_chatroom_message(messages):
a[9]: msgSvrId,
a[10]: BytesExtra,
a[11]: CompressContent,
a[12]: msg_sender, ContactPC ContactDefault 类型这个才是群聊里的信息发送人不是群聊或者自己是发送者没有这个字段
a[12]: DisplayContent,
a[13]: msg_sender, ContactPC ContactDefault 类型这个才是群聊里的信息发送人不是群聊或者自己是发送者没有这个字段
'''
updated_messages = [] # 用于存储修改后的消息列表
for row in messages:
@ -157,13 +158,15 @@ class Msg:
a[9]: msgSvrId,
a[10]: BytesExtra,
a[11]: CompressContent,
a[12]: DisplayContent,
a[13]: 联系人的类如果是群聊就有不是的话没有这个字段
"""
if not self.open_flag:
return None
if time_range:
start_time, end_time = time_range
sql = f'''
select localId,TalkerId,Type,SubType,IsSender,CreateTime,Status,StrContent,strftime('%Y-%m-%d %H:%M:%S',CreateTime,'unixepoch','localtime') as StrTime,MsgSvrID,BytesExtra,CompressContent
select localId,TalkerId,Type,SubType,IsSender,CreateTime,Status,StrContent,strftime('%Y-%m-%d %H:%M:%S',CreateTime,'unixepoch','localtime') as StrTime,MsgSvrID,BytesExtra,CompressContent,DisplayContent
from MSG
where StrTalker=?
{'AND CreateTime>' + str(start_time) + ' AND CreateTime<' + str(end_time) if time_range else ''}

View File

@ -118,12 +118,12 @@ class ExporterBase(QThread):
def get_avatar_path(self, is_send, message, is_absolute_path=False) -> str:
if is_absolute_path:
if self.contact.is_chatroom:
avatar = message[12].avatar_path
avatar = message[13].avatar_path
else:
avatar = Me().avatar_path if is_send else self.contact.avatar_path
else:
if self.contact.is_chatroom:
avatar = message[12].smallHeadImgUrl
avatar = message[13].smallHeadImgUrl
else:
avatar = Me().smallHeadImgUrl if is_send else self.contact.smallHeadImgUrl
return avatar
@ -133,7 +133,7 @@ class ExporterBase(QThread):
if is_send:
display_name = Me().name
else:
display_name = message[12].remark
display_name = message[13].remark
else:
display_name = Me().name if is_send else self.contact.remark
return escape_js_and_html(display_name)

View File

@ -107,7 +107,8 @@ class PackageMsg:
a[9]: msgSvrId,
a[10]: BytesExtra,
a[11]: CompressContent,
a[12]: msg_sender, ContactPC ContactDefault 类型这个才是群聊里的信息发送人不是群聊或者自己是发送者没有这个字段
a[12]: DisplayContent,
a[13]: msg_sender, ContactPC ContactDefault 类型这个才是群聊里的信息发送人不是群聊或者自己是发送者没有这个字段
'''
updated_messages = [] # 用于存储修改后的消息列表
messages = msg_db.get_messages(chatroom_wxid)

View File

@ -83,13 +83,13 @@ class ChatInfo(QWidget):
def get_avatar_path(self, is_send, message, is_absolute_path=False) -> str:
if self.contact.is_chatroom:
avatar = message[12].smallHeadImgUrl
avatar = message[13].smallHeadImgUrl
else:
avatar = Me().smallHeadImgUrl if is_send else self.contact.smallHeadImgUrl
if is_absolute_path:
if self.contact.is_chatroom:
# message[12].save_avatar()
avatar = message[12].avatar
# message[13].save_avatar()
avatar = message[13].avatar
else:
avatar = Me().avatar if is_send else self.contact.avatar
return avatar
@ -99,7 +99,7 @@ class ChatInfo(QWidget):
if is_send:
display_name = Me().name
else:
display_name = message[12].remark
display_name = message[13].remark
else:
display_name = None
return display_name

View File

@ -22,7 +22,9 @@ types = {
'音乐与音频': 4903,
'文件': 4906,
'分享卡片': 4905,
'拍一拍等系统消息': 10000
'转账': 492000,
'音视频通话': 50,
'拍一拍等系统消息': 10000,
}
Stylesheet = """
QPushButton{
@ -53,7 +55,7 @@ class ExportDialog(QDialog, Ui_Dialog):
if file_type == 'html':
self.export_type = Output.HTML
self.export_choices = {"文本": True, "图片": True, "语音": False, "视频": False, "表情包": False,
'音乐与音频': False, '分享卡片': False, '文件': False,
'音乐与音频': False, '分享卡片': False, '文件': False, '转账': False, '音视频通话': False,
'拍一拍等系统消息': True} # 定义导出的数据类型,默认全部选择
elif file_type == 'csv':
self.export_type = Output.CSV

View File

@ -26,7 +26,9 @@ types = {
'音乐与音频': 4903,
'文件': 4906,
'分享卡片': 4905,
'拍一拍等系统消息': 10000
'转账': 492000,
'音视频通话': 50,
'拍一拍等系统消息': 10000,
}
file_format = {
'Docx': Output.DOCX,
@ -69,7 +71,7 @@ class ExportDialog(QDialog, Ui_Dialog):
self.textBrowser.setVerticalScrollBar(scroll_bar)
self.export_choices = {"文本": True, "图片": True, "语音": False, "视频": False, "表情包": False,
'音乐与音频': False, '分享卡片': False, '文件': False,
'拍一拍等系统消息': True} # 定义导出的数据类型,默认全部选择
'转账': False, '音视频通话': False, '拍一拍等系统消息': True} # 定义导出的数据类型
self.setWindowTitle(title)
self.checkBox_word.setEnabled(False)
self.checkBox_word.setText('Docx(暂时不可用)')

View File

@ -19,31 +19,32 @@ def decompress_CompressContent(data):
:return:
"""
if data is None or not isinstance(data, bytes):
return ''
return ""
try:
dst = lz4.block.decompress(data, uncompressed_size=len(data) << 10)
decoded_string = dst.decode().replace('\x00', '') # Remove any null characters
decoded_string = dst.decode().replace("\x00", "") # Remove any null characters
except:
print("Decompression failed: potentially corrupt input or insufficient buffer size.")
return ''
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 ''
return ""
# 转义HTML特殊字符
html_escaped = html.escape(input_str, quote=False)
# 手动处理JavaScript转义字符
js_escaped = (
html_escaped
.replace("\\", "\\\\")
html_escaped.replace("\\", "\\\\")
.replace("'", r"\'")
.replace('"', r'\"')
.replace("\n", r'\n')
.replace("\r", r'\r')
.replace("\t", r'\t')
.replace('"', r"\"")
.replace("\n", r"\n")
.replace("\r", r"\r")
.replace("\t", r"\t")
)
return js_escaped
@ -53,168 +54,232 @@ def parser_reply(data: bytes):
xml_content = decompress_CompressContent(data)
if not xml_content:
return {
'type': 57,
'title': "发生错误",
'refer': {
'type': '1',
'content': '引用错误',
'displayname': '用户名',
"type": 57,
"title": "发生错误",
"refer": {
"type": "1",
"content": "引用错误",
"displayname": "用户名",
},
"is_error": True
"is_error": True,
}
try:
root = ET.XML(xml_content)
appmsg = root.find('appmsg')
msg_type = int(appmsg.find('type').text)
title = appmsg.find('title').text
refermsg_content = appmsg.find('refermsg').find('content').text
refermsg_type = int(appmsg.find('refermsg').find('type').text)
refermsg_displayname = appmsg.find('refermsg').find('displayname').text
appmsg = root.find("appmsg")
msg_type = int(appmsg.find("type").text)
title = appmsg.find("title").text
refermsg_content = appmsg.find("refermsg").find("content").text
refermsg_type = int(appmsg.find("refermsg").find("type").text)
refermsg_displayname = appmsg.find("refermsg").find("displayname").text
return {
'type': msg_type,
'title': title,
'refer': None if refermsg_type != 1 else {
'type': refermsg_type,
'content': refermsg_content.lstrip("\n"),
'displayname': refermsg_displayname,
"type": msg_type,
"title": title,
"refer": None
if refermsg_type != 1
else {
"type": refermsg_type,
"content": refermsg_content.lstrip("\n"),
"displayname": refermsg_displayname,
},
"is_error": False
"is_error": False,
}
except:
return {
'type': 57,
'title': "发生错误",
'refer': {
'type': '1',
'content': '引用错误',
'displayname': '用户名',
"type": 57,
"title": "发生错误",
"refer": {
"type": "1",
"content": "引用错误",
"displayname": "用户名",
},
"is_error": True
"is_error": True,
}
def music_share(data: bytes):
xml_content = decompress_CompressContent(data)
if not xml_content:
return {
'type': 3,
'title': "发生错误",
"is_error": True
}
return {"type": 3, "title": "发生错误", "is_error": True}
try:
root = ET.XML(xml_content)
appmsg = root.find('appmsg')
msg_type = int(appmsg.find('type').text)
title = appmsg.find('title').text
appmsg = root.find("appmsg")
msg_type = int(appmsg.find("type").text)
title = appmsg.find("title").text
if len(title) >= 39:
title = title[:38] + '...'
artist = appmsg.find('des').text
link_url = appmsg.find('url').text # 链接地址
audio_url = get_audio_url(appmsg.find('dataurl').text) # 播放地址
title = title[:38] + "..."
artist = appmsg.find("des").text
link_url = appmsg.find("url").text # 链接地址
audio_url = get_audio_url(appmsg.find("dataurl").text) # 播放地址
website_name = get_website_name(link_url)
return {
'type': msg_type,
'title': escape_js_and_html(title),
'artist': escape_js_and_html(artist),
'link_url': link_url,
'audio_url': audio_url,
'website_name': escape_js_and_html(website_name),
"is_error": False
"type": msg_type,
"title": escape_js_and_html(title),
"artist": escape_js_and_html(artist),
"link_url": link_url,
"audio_url": audio_url,
"website_name": escape_js_and_html(website_name),
"is_error": False,
}
except Exception as e:
print(f"Music Share Error: {e}")
return {
'type': 3,
'title': "发生错误",
"is_error": True
}
return {"type": 3, "title": "发生错误", "is_error": True}
def share_card(bytesExtra, compress_content_):
title, des, url, show_display_name, thumbnail, app_logo = '', '', '', '', '', ''
title, des, url, show_display_name, thumbnail, app_logo = "", "", "", "", "", ""
try:
xml = decompress_CompressContent(compress_content_)
root = ET.XML(xml)
appmsg = root.find('appmsg')
title = appmsg.find('title').text
appmsg = root.find("appmsg")
title = appmsg.find("title").text
try:
des = appmsg.find('des').text
des = appmsg.find("des").text
except:
des = ''
url = appmsg.find('url').text
appinfo = root.find('appinfo')
show_display_name = appmsg.find('sourcedisplayname')
sourceusername = appmsg.find('sourceusername')
des = ""
url = appmsg.find("url").text
appinfo = root.find("appinfo")
show_display_name = appmsg.find("sourcedisplayname")
sourceusername = appmsg.find("sourceusername")
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
show_display_name = appinfo.find("appname").text
msg_bytes = MessageBytesExtra()
msg_bytes.ParseFromString(bytesExtra)
app_logo = ''
thumbnail = ''
app_logo = ""
thumbnail = ""
for tmp in msg_bytes.message2:
if tmp.field1 == 3:
thumbnail = tmp.field2
thumbnail = "\\".join(thumbnail.split('\\')[1:])
thumbnail = "\\".join(thumbnail.split("\\")[1:])
if tmp.field2 == 4:
app_logo = tmp.field2
app_logo = "\\".join(app_logo.split('\\')[1:])
app_logo = "\\".join(app_logo.split("\\")[1:])
if sourceusername is not None:
from app.DataBase import micro_msg_db # 放上面会导致循环依赖
contact = micro_msg_db.get_contact_by_username(sourceusername.text)
if contact:
app_logo = contact[7]
finally:
return {
'title': escape_js_and_html(title),
'description': escape_js_and_html(des),
'url': escape_js_and_html(url),
'app_name': escape_js_and_html(show_display_name),
'thumbnail': thumbnail,
'app_logo': app_logo
"title": escape_js_and_html(title),
"description": escape_js_and_html(des),
"url": escape_js_and_html(url),
"app_name": escape_js_and_html(show_display_name),
"thumbnail": thumbnail,
"app_logo": app_logo,
}
def transfer_decompress(compress_content_):
"""
return dict
feedesc: 钱数str类型包含一个前缀币种符号除人民币之外未测试;
pay_memo: 转账备注;
receiver_username: 接受转账人的 wxid; 因为电脑上只有私聊页面会显示收款所以这个字段没有也罢不要轻易使用因为可能为空
paysubtype: int 类型1 为发出转账3 为接受转账4 为退还转账;
"""
feedesc, pay_memo, receiver_username, paysubtype = "", "", "", ""
try:
xml = decompress_CompressContent(compress_content_)
root = ET.XML(xml)
appmsg = root.find("appmsg")
wcpayinfo = appmsg.find("wcpayinfo")
paysubtype = int(wcpayinfo.find("paysubtype").text)
feedesc = wcpayinfo.find("feedesc").text
pay_memo = wcpayinfo.find("pay_memo").text
receiver_username = wcpayinfo.find("receiver_username").text
finally:
return {
"feedesc": feedesc,
"pay_memo": escape_js_and_html(pay_memo),
"receiver_username": receiver_username,
"paysubtype": paysubtype,
}
def call_decompress(is_send, bytes_extra, display_content, str_content): # 音视频通话
"""
return dict
call_type: int 类型0 为视频1为语音; 返回为 2 是未知错误
display_content: str 类型页面显示的话;
"""
call_type = 2
call_length = 0
msg_bytes = MessageBytesExtra()
msg_bytes.ParseFromString(bytes_extra)
# message2 字段 1: 发送人wxid; 字段 3: "1"是语音,"0"是视频; 字段 4: 通话时长
for i in msg_bytes.message2:
if i.field1 == 3:
call_type = int(i.field2)
elif i.field1 == 4:
call_length = int(i.field2)
try:
if display_content == "":
if str_content == "11":
h, m, s = (
call_length // 3600,
(call_length % 3600) // 60,
call_length % 60,
)
display_content = f"通话时长 {f'{h}:' if h else ''}{m:02d}:{s:02d}"
else:
display_content = {
"5": ("" if is_send else "对方") + "已取消",
"8": ("对方" if is_send else "") + "已拒绝",
"7": "已在其他设备接听",
"12": "已在其他设备拒绝",
}[str_content]
except KeyError:
display_content = "未知类型,您可以把这条消息对应的微信界面消息反馈给我们"
return {
"call_type": call_type,
"display_content": display_content,
}
def get_website_name(url):
parsed_url = urlparse(url)
domain = f"{parsed_url.scheme}://{parsed_url.netloc}"
website_name = ''
website_name = ""
try:
response = requests.get(domain, allow_redirects=False)
if response.status_code == 200:
soup = BeautifulSoup(response.content, 'html.parser')
soup = BeautifulSoup(response.content, "html.parser")
website_name = soup.title.string.strip()
elif response.status_code == 302:
domain = response.headers['Location']
domain = response.headers["Location"]
response = requests.get(domain, allow_redirects=False)
soup = BeautifulSoup(response.content, 'html.parser')
soup = BeautifulSoup(response.content, "html.parser")
website_name = soup.title.string.strip()
else:
response = requests.get(url, allow_redirects=False)
if response.status_code == 200:
soup = BeautifulSoup(response.content, 'html.parser')
soup = BeautifulSoup(response.content, "html.parser")
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
def get_audio_url(url):
path = ''
path = ""
try:
response = requests.get(url, allow_redirects=False)
# 检查响应状态码
if response.status_code == 302:
path = response.headers['Location']
path = response.headers["Location"]
elif response.status_code == 200:
print('音乐文件已失效,url:' + url)
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
@ -223,45 +288,37 @@ def get_audio_url(url):
def file(bytes_extra, compress_content, output_path):
xml_content = decompress_CompressContent(compress_content)
if not xml_content:
return {
'type': 6,
'title': "发生错误",
"is_error": True
}
return {"type": 6, "title": "发生错误", "is_error": True}
try:
root = ET.XML(xml_content)
appmsg = root.find('appmsg')
msg_type = int(appmsg.find('type').text)
file_name = appmsg.find('title').text
appmsg = root.find("appmsg")
msg_type = int(appmsg.find("type").text)
file_name = appmsg.find("title").text
pattern = r'[\\/:*?"<>|\r\n]+'
file_name = re.sub(pattern, "_", file_name)
appattach = appmsg.find('appattach')
file_len = int(appattach.find('totallen').text)
app_name = ''
appattach = appmsg.find("appattach")
file_len = int(appattach.find("totallen").text)
app_name = ""
file_len = format_bytes(file_len)
file_ext = appattach.find('fileext').text
file_ext = appattach.find("fileext").text
if root.find("appinfo") is not None:
app_info = root.find('appinfo')
app_name = app_info.find('appname').text
app_info = root.find("appinfo")
app_name = app_info.find("appname").text
if app_name is None:
app_name = ''
app_name = ""
file_path = get_file(bytes_extra, file_name, output_path)
return {
'type': msg_type,
'file_name': escape_js_and_html(file_name),
'file_len': file_len,
'file_ext': file_ext,
'file_path': file_path,
'app_name': escape_js_and_html(app_name),
"is_error": False
"type": msg_type,
"file_name": escape_js_and_html(file_name),
"file_len": file_len,
"file_ext": file_ext,
"file_path": file_path,
"app_name": escape_js_and_html(app_name),
"is_error": False,
}
except Exception as e:
print(f"File Get Info Error: {e}")
return {
'type': 6,
'title': "发生错误",
"is_error": True
}
return {"type": 6, "title": "发生错误", "is_error": True}
def format_bytes(size):