补充已过期或丢失文件的处理,添加文件和大小和类型等信息,集中处理一些用到的图标
@ -12,13 +12,24 @@ from app.DataBase.package_msg import PackageMsg
|
|||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.person import Me
|
from app.person import Me
|
||||||
from app.util import path
|
from app.util import path
|
||||||
from app.util.compress_content import parser_reply, share_card, music_share
|
from app.util.compress_content import parser_reply, share_card, music_share, file
|
||||||
from app.util.emoji import get_emoji_url
|
from app.util.emoji import get_emoji_url
|
||||||
from app.util.file import get_file
|
|
||||||
from app.util.image import get_image_path, get_image
|
from app.util.image import get_image_path, get_image
|
||||||
from app.util.music import get_music_path
|
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'],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class HtmlExporter(ExporterBase):
|
class HtmlExporter(ExporterBase):
|
||||||
def text(self, doc, message):
|
def text(self, doc, message):
|
||||||
type_ = message[2]
|
type_ = message[2]
|
||||||
@ -95,20 +106,32 @@ class HtmlExporter(ExporterBase):
|
|||||||
def file(self, doc, message):
|
def file(self, doc, message):
|
||||||
origin_docx_path = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}"
|
origin_docx_path = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}"
|
||||||
bytesExtra = message[10]
|
bytesExtra = message[10]
|
||||||
|
compress_content = message[11]
|
||||||
str_time = message[8]
|
str_time = message[8]
|
||||||
is_send = message[4]
|
is_send = message[4]
|
||||||
timestamp = message[5]
|
timestamp = message[5]
|
||||||
is_chatroom = 1 if self.contact.is_chatroom else 0
|
is_chatroom = 1 if self.contact.is_chatroom else 0
|
||||||
avatar = self.get_avatar_path(is_send, message)
|
avatar = self.get_avatar_path(is_send, message)
|
||||||
display_name = self.get_display_name(is_send, message)
|
display_name = self.get_display_name(is_send, message)
|
||||||
link = get_file(bytesExtra, thumb=True, output_path=origin_docx_path + '/file')
|
file_info = file(bytesExtra, compress_content, output_path=origin_docx_path + '/file')
|
||||||
file_name = ''
|
if file_info.get('is_error') == False:
|
||||||
file_path = './icon/file.png'
|
icon_path = None
|
||||||
if link != "":
|
for icon, extensions in icon_files.items():
|
||||||
file_name = os.path.basename(link)
|
if file_info.get('file_ext') in extensions:
|
||||||
link = './file/' + file_name
|
icon_path = icon
|
||||||
|
break
|
||||||
|
# 如果没有与文件后缀匹配的图标,则使用默认图标
|
||||||
|
if icon_path is None:
|
||||||
|
default_icon = './icon/file.png'
|
||||||
|
icon_path = default_icon
|
||||||
|
file_path = file_info.get('file_path')
|
||||||
|
if file_path != "":
|
||||||
|
file_path = './file/' + file_info.get('file_name')
|
||||||
doc.write(
|
doc.write(
|
||||||
f'''{{ type:49, text: '{file_path}',is_send:{is_send},avatar_path:'{avatar}',timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}',link: '{link}',sub_type:6,file_name: '{file_name}'}},'''
|
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')}'}},'''
|
||||||
)
|
)
|
||||||
|
|
||||||
def refermsg(self, doc, message):
|
def refermsg(self, doc, message):
|
||||||
@ -200,6 +223,7 @@ class HtmlExporter(ExporterBase):
|
|||||||
timestamp = message[5]
|
timestamp = message[5]
|
||||||
content = music_share(message[11])
|
content = music_share(message[11])
|
||||||
music_path = ''
|
music_path = ''
|
||||||
|
if content.get('is_error') == False:
|
||||||
if content.get('audio_url') != '':
|
if content.get('audio_url') != '':
|
||||||
music_path = get_music_path(content.get('audio_url'), content.get('title'),
|
music_path = get_music_path(content.get('audio_url'), content.get('title'),
|
||||||
output_path=origin_docx_path + '/music')
|
output_path=origin_docx_path + '/music')
|
||||||
@ -209,7 +233,6 @@ class HtmlExporter(ExporterBase):
|
|||||||
is_chatroom = 1 if self.contact.is_chatroom else 0
|
is_chatroom = 1 if self.contact.is_chatroom else 0
|
||||||
avatar = self.get_avatar_path(is_send, message)
|
avatar = self.get_avatar_path(is_send, message)
|
||||||
display_name = self.get_display_name(is_send, message)
|
display_name = self.get_display_name(is_send, message)
|
||||||
if content.get('is_error') == False:
|
|
||||||
doc.write(
|
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')}',
|
timestamp:{timestamp},is_chatroom:{is_chatroom},displayname:'{display_name}',sub_type:3,title:'{content.get('title')}',
|
||||||
|
@ -5,6 +5,7 @@ import shutil
|
|||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
|
import filecmp
|
||||||
from re import findall
|
from re import findall
|
||||||
|
|
||||||
import docx
|
import docx
|
||||||
@ -51,21 +52,24 @@ def makedirs(path):
|
|||||||
os.makedirs(os.path.join(path, 'avatar'), exist_ok=True)
|
os.makedirs(os.path.join(path, 'avatar'), exist_ok=True)
|
||||||
os.makedirs(os.path.join(path, 'music'), exist_ok=True)
|
os.makedirs(os.path.join(path, 'music'), exist_ok=True)
|
||||||
os.makedirs(os.path.join(path, 'icon'), exist_ok=True)
|
os.makedirs(os.path.join(path, 'icon'), exist_ok=True)
|
||||||
file = './app/resources/data/file.png'
|
resource_dir = os.path.join('app', 'resources', 'data', 'icons')
|
||||||
if not os.path.exists(file):
|
target_folder = os.path.join(path, 'icon')
|
||||||
resource_dir = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__)))
|
# 拷贝一些必备的图标
|
||||||
file = os.path.join(resource_dir, 'app', 'resources', 'data', 'file.png')
|
for root, dirs, files in os.walk(resource_dir):
|
||||||
shutil.copy(file, path + '/icon/file.png')
|
relative_path = os.path.relpath(root, resource_dir)
|
||||||
play_file = './app/resources/data/play.png'
|
target_path = os.path.join(target_folder, relative_path)
|
||||||
if not os.path.exists(play_file):
|
|
||||||
resource_dir = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__)))
|
# 遍历文件夹中的文件
|
||||||
play_file = os.path.join(resource_dir, 'app', 'resources', 'data', 'play.png')
|
for file in files:
|
||||||
shutil.copy(play_file, path + '/icon/play.png')
|
source_file_path = os.path.join(root, file)
|
||||||
pause_file = './app/resources/data/pause.png'
|
target_file_path = os.path.join(target_path, file)
|
||||||
if not os.path.exists(pause_file):
|
if not os.path.exists(target_file_path):
|
||||||
resource_dir = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__)))
|
shutil.copy(source_file_path, target_file_path)
|
||||||
pause_file = os.path.join(resource_dir, 'app', 'resources', 'data', 'pause.png')
|
else:
|
||||||
shutil.copy(pause_file, path + '/icon/pause.png')
|
# 比较文件内容
|
||||||
|
if not filecmp.cmp(source_file_path, target_file_path, shallow=False):
|
||||||
|
# 文件内容不一致,进行覆盖拷贝
|
||||||
|
shutil.copy(source_file_path, target_file_path)
|
||||||
|
|
||||||
|
|
||||||
def escape_js_and_html(input_str):
|
def escape_js_and_html(input_str):
|
||||||
|
BIN
app/resources/data/icons/csv.png
Normal file
After Width: | Height: | Size: 6.4 KiB |
BIN
app/resources/data/icons/excel.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
app/resources/data/icons/file.png
Normal file
After Width: | Height: | Size: 568 B |
BIN
app/resources/data/icons/pause.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
app/resources/data/icons/pdf.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
app/resources/data/icons/play.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
app/resources/data/icons/ppt.png
Normal file
After Width: | Height: | Size: 5.0 KiB |
BIN
app/resources/data/icons/txt.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
app/resources/data/icons/word.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
app/resources/data/icons/zip.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 386 B |
Before Width: | Height: | Size: 716 B |
@ -187,30 +187,7 @@ audio{
|
|||||||
margin-right: 9px;
|
margin-right: 9px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-file {
|
.chat-music-audio, .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;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-music-audio {
|
|
||||||
width: 300px;
|
width: 300px;
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -222,8 +199,11 @@ audio{
|
|||||||
height: 100px;
|
height: 100px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
.chat-file img{
|
||||||
|
width: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
.chat-music-audio .player-box {
|
.chat-music-audio .player-box,.chat-file .file-box {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -231,43 +211,43 @@ audio{
|
|||||||
height: 80px;
|
height: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-music-audio .player-original {
|
.chat-music-audio .player-original ,.chat-file .app-info{
|
||||||
border-top: 1px solid #ede3e3;
|
border-top: 1px solid #ede3e3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-music-audio .player-original p {
|
.chat-music-audio .player-original p ,.chat-file .app-info p{
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-music-audio .player-controls {
|
.chat-music-audio .player-controls, .chat-file .file-img{
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;;
|
align-items: center;;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-music-audio .flex1 {
|
.chat-music-audio .flex1,.chat-file .flex1 {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-music-audio .flex2 {
|
.chat-music-audio .flex2 ,.chat-file .flex2{
|
||||||
flex: 2;
|
flex: 2;
|
||||||
justify-content: end;
|
justify-content: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-music-audio .player-info {
|
.chat-music-audio .player-info ,.chat-file .file-info{
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
flex-basis: 200px;
|
flex-basis: 200px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-music-audio .song-title {
|
.chat-music-audio .song-title,.chat-file .file-name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-music-audio .artist {
|
.chat-music-audio .artist ,.chat-file .file-size{
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
@ -749,12 +729,26 @@ input {
|
|||||||
function messageFileBox(message) {
|
function messageFileBox(message) {
|
||||||
const messageFileTag = document.createElement('div');
|
const messageFileTag = document.createElement('div');
|
||||||
messageFileTag.className = `chat-file`;
|
messageFileTag.className = `chat-file`;
|
||||||
if (message.link !== ''){
|
messageFileTag.onclick = function (event) {
|
||||||
messageFileTag.innerHTML = `
|
if (message.text !== '') {
|
||||||
<a href="${message.link}" target="_blank"><span>${message.file_name}</span><img loading="lazy" src="${message.text}"/></a>`
|
window.open(message.text, '_blank');
|
||||||
}else{
|
}else{
|
||||||
messageFileTag.innerHTML = `<div><span>文件已丢失</span><img loading="lazy" src="${message.text}"/></div>`;
|
alert("文件可能丢失、过期或不存放在该主机上")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (message.file_name.length >= 26) {
|
||||||
|
message.file_name = message.file_name.slice(0, 25) + '...'
|
||||||
|
}
|
||||||
|
messageFileTag.innerHTML = `<div class="file-box">
|
||||||
|
<div class="file-info flex1">
|
||||||
|
<div class="file-title">${message.file_name}</div>
|
||||||
|
<div class="file-size">${message.file_size}</div>
|
||||||
|
</div>
|
||||||
|
<div class="file-img flex2">
|
||||||
|
<img src="${message.icon_path}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="app-info"><p>${message.app_name}</p></div>`
|
||||||
return messageFileTag;
|
return messageFileTag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,10 +4,13 @@ import xml.etree.ElementTree as ET
|
|||||||
import lz4.block
|
import lz4.block
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
import re
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from app.DataBase.hard_link import parseBytes
|
from app.DataBase.hard_link import parseBytes
|
||||||
|
from ..util.file import get_file
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def decompress_CompressContent(data):
|
def decompress_CompressContent(data):
|
||||||
@ -206,3 +209,58 @@ def get_audio_url(url):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Get Audio Url Error: {e}")
|
print(f"Get Audio Url Error: {e}")
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
root = ET.XML(xml_content)
|
||||||
|
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 = ''
|
||||||
|
file_len = format_bytes(file_len)
|
||||||
|
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
|
||||||
|
if app_name is None:
|
||||||
|
app_name = ''
|
||||||
|
file_path = get_file(bytes_extra, file_name, output_path)
|
||||||
|
return {
|
||||||
|
'type': msg_type,
|
||||||
|
'file_name': file_name,
|
||||||
|
'file_len': file_len,
|
||||||
|
'file_ext': file_ext,
|
||||||
|
'file_path': file_path,
|
||||||
|
'app_name': app_name,
|
||||||
|
"is_error": False
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
print(f"File Get Info Error: {e}")
|
||||||
|
return {
|
||||||
|
'type': 6,
|
||||||
|
'title': "发生错误",
|
||||||
|
"is_error": True
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def format_bytes(size):
|
||||||
|
units = ["B", "KB", "MB", "GB"]
|
||||||
|
|
||||||
|
def convert_bytes(size, unit_index):
|
||||||
|
if size < 1024 or unit_index >= len(units) - 1:
|
||||||
|
return size, unit_index
|
||||||
|
return convert_bytes(size / 1024, unit_index + 1)
|
||||||
|
final_size, final_unit_index = convert_bytes(size, 0)
|
||||||
|
return f"{final_size:.2f} {units[final_unit_index]}"
|
||||||
|
@ -20,25 +20,21 @@ class File:
|
|||||||
self.open_flag = False
|
self.open_flag = False
|
||||||
|
|
||||||
|
|
||||||
def get_file(bytes_extra, thumb=False, output_path=root_path) -> str:
|
def get_file(bytes_extra, file_name, output_path=root_path) -> str:
|
||||||
try:
|
try:
|
||||||
msg_bytes = MessageBytesExtra()
|
msg_bytes = MessageBytesExtra()
|
||||||
msg_bytes.ParseFromString(bytes_extra)
|
msg_bytes.ParseFromString(bytes_extra)
|
||||||
file_original_path = ''
|
|
||||||
file_path = ''
|
file_path = ''
|
||||||
file_name = ''
|
|
||||||
real_path = ''
|
real_path = ''
|
||||||
if len(msg_bytes.message2) > 0:
|
if len(msg_bytes.message2) > 0:
|
||||||
file_field = msg_bytes.message2[-1].field2
|
for filed in msg_bytes.message2:
|
||||||
if file_field.find('sec_msg_node') == -1:
|
if filed.field1 == 4:
|
||||||
file_original_path = file_field
|
file_original_path = filed.field2
|
||||||
file_name = os.path.basename(file_original_path)
|
|
||||||
if file_name != '' and file_name != Me().wxid:
|
|
||||||
file_path = os.path.join(output_path, file_name)
|
file_path = os.path.join(output_path, file_name)
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
print('文件' + file_path + '已存在')
|
# print('文件' + file_path + '已存在')
|
||||||
return file_path
|
return file_path
|
||||||
if os.path.isabs(file_original_path):
|
if os.path.isabs(file_original_path): # 绝对路径可能迁移过文件目录,也可能存在其他位置
|
||||||
if os.path.exists(file_original_path):
|
if os.path.exists(file_original_path):
|
||||||
real_path = file_original_path
|
real_path = file_original_path
|
||||||
else: # 如果没找到再判断一次是否是迁移了目录
|
else: # 如果没找到再判断一次是否是迁移了目录
|
||||||
|