mirror of
https://github.com/LC044/WeChatMsg
synced 2025-02-22 19:02:17 +08:00
html支持导出分享的卡片链接
This commit is contained in:
parent
38016b895d
commit
240e296bd3
@ -4,6 +4,7 @@ import sqlite3
|
|||||||
import threading
|
import threading
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
from app.DataBase.hard_link import parseBytes
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.util.compress_content import parser_reply
|
from app.util.compress_content import parser_reply
|
||||||
from app.util.protocbuf.msg_pb2 import MessageBytesExtra
|
from app.util.protocbuf.msg_pb2 import MessageBytesExtra
|
||||||
@ -629,4 +630,33 @@ if __name__ == '__main__':
|
|||||||
msg.init_database()
|
msg.init_database()
|
||||||
wxid = 'wxid_0o18ef858vnu22'
|
wxid = 'wxid_0o18ef858vnu22'
|
||||||
wxid = '24521163022@chatroom'
|
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)
|
@ -20,7 +20,7 @@ from ..DataBase import media_msg_db, hard_link_db, micro_msg_db, msg_db
|
|||||||
from ..log import logger
|
from ..log import logger
|
||||||
from ..person import Me
|
from ..person import Me
|
||||||
from ..util import path
|
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.emoji import get_emoji_url
|
||||||
from ..util.file import get_file
|
from ..util.file import get_file
|
||||||
from ..util.music import get_music_path
|
from ..util.music import get_music_path
|
||||||
@ -123,12 +123,14 @@ class Output(QThread):
|
|||||||
@return:
|
@return:
|
||||||
"""
|
"""
|
||||||
return
|
return
|
||||||
|
|
||||||
def output_emoji(self):
|
def output_emoji(self):
|
||||||
"""
|
"""
|
||||||
导出全部表情包
|
导出全部表情包
|
||||||
@return:
|
@return:
|
||||||
"""
|
"""
|
||||||
return
|
return
|
||||||
|
|
||||||
def to_csv_all(self):
|
def to_csv_all(self):
|
||||||
"""
|
"""
|
||||||
导出全部聊天记录到CSV
|
导出全部聊天记录到CSV
|
||||||
@ -658,6 +660,41 @@ class ChildThread(QThread):
|
|||||||
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):
|
||||||
|
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):
|
def to_csv(self):
|
||||||
origin_docx_path = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}"
|
origin_docx_path = f"{os.path.abspath('.')}/data/聊天记录/{self.contact.remark}"
|
||||||
os.makedirs(origin_docx_path, exist_ok=True)
|
os.makedirs(origin_docx_path, exist_ok=True)
|
||||||
@ -732,6 +769,8 @@ class ChildThread(QThread):
|
|||||||
self.file(f, message)
|
self.file(f, message)
|
||||||
elif type_ == 49 and sub_type == 3 and self.message_types.get(4903):
|
elif type_ == 49 and sub_type == 3 and self.message_types.get(4903):
|
||||||
self.music_share(f, message)
|
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.write(html_end)
|
||||||
f.close()
|
f.close()
|
||||||
self.okSignal.emit(1)
|
self.okSignal.emit(1)
|
||||||
|
BIN
app/resources/data/pause.png
Normal file
BIN
app/resources/data/pause.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 386 B |
BIN
app/resources/data/play.png
Normal file
BIN
app/resources/data/play.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 716 B |
@ -390,6 +390,57 @@ input {
|
|||||||
color: #555;
|
color: #555;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 分享的卡片 */
|
||||||
|
.card {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
max-width: 400px;
|
||||||
|
width: 400px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: left;
|
||||||
|
margin: 20px;
|
||||||
|
}
|
||||||
|
.card a {
|
||||||
|
text-decoration: none; /* 去掉链接的下划线 */
|
||||||
|
color: inherit; /* 继承父元素的文字颜色 */
|
||||||
|
|
||||||
|
}
|
||||||
|
.card-content {
|
||||||
|
padding: 20px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
.description{
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.link-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-logo {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
body{
|
body{
|
||||||
display: flex; /* 使用 Flex 布局 */
|
display: flex; /* 使用 Flex 布局 */
|
||||||
@ -498,7 +549,7 @@ input {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const itemsPerPage = 100; // 每页显示的元素个数
|
const itemsPerPage = 1000; // 每页显示的元素个数
|
||||||
let currentPage = 1; // 当前页
|
let currentPage = 1; // 当前页
|
||||||
var reachedBottom = false; // 到达底部的标记
|
var reachedBottom = false; // 到达底部的标记
|
||||||
var lastScrollTop = 0;
|
var lastScrollTop = 0;
|
||||||
@ -638,6 +689,7 @@ input {
|
|||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成各类标签的函数
|
// 生成各类标签的函数
|
||||||
function messageBubble(message, side) {
|
function messageBubble(message, side) {
|
||||||
const messageBubbleTag = document.createElement('div');
|
const messageBubbleTag = document.createElement('div');
|
||||||
@ -687,6 +739,12 @@ input {
|
|||||||
messageAudioTag.innerHTML = `<audio src="${message.text}" controls></audio>`;
|
messageAudioTag.innerHTML = `<audio src="${message.text}" controls></audio>`;
|
||||||
return messageAudioTag;
|
return messageAudioTag;
|
||||||
}
|
}
|
||||||
|
function messageCard(message) {
|
||||||
|
const messageTag = document.createElement('div');
|
||||||
|
messageTag.className = `card`;
|
||||||
|
messageTag.innerHTML = `<a href="${message.url}" target="_blank"><div class="card-content"><h2>${message.title}</h2><div class="description"><p>${message.description}</p><img class="thumbnail" src="${message.thumbnail}" alt="Thumbnail"></div></div><div class="link-info"><img class="app-logo" src="${message.app_logo}" alt="App Logo"><span class="app-name">${message.app_name}</span></div></a>`;
|
||||||
|
return messageTag;
|
||||||
|
}
|
||||||
function messageFileBox(message) {
|
function messageFileBox(message) {
|
||||||
const messageFileTag = document.createElement('div');
|
const messageFileTag = document.createElement('div');
|
||||||
messageFileTag.className = `chat-file`;
|
messageFileTag.className = `chat-file`;
|
||||||
@ -835,7 +893,7 @@ input {
|
|||||||
messageElement.appendChild(message.is_send ? messageContent : avatarTag);
|
messageElement.appendChild(message.is_send ? messageContent : avatarTag);
|
||||||
messageElement.appendChild(message.is_send ? avatarTag : messageContent);
|
messageElement.appendChild(message.is_send ? avatarTag : messageContent);
|
||||||
}
|
}
|
||||||
if (message.sub_type == 6) {
|
else if (message.sub_type == 6) {
|
||||||
// displayname 和 file
|
// displayname 和 file
|
||||||
messageContent.className = `content-wrapper content-wrapper-${side}`;
|
messageContent.className = `content-wrapper content-wrapper-${side}`;
|
||||||
if (message.is_chatroom && !message.is_send) {
|
if (message.is_chatroom && !message.is_send) {
|
||||||
@ -848,7 +906,7 @@ input {
|
|||||||
messageElement.appendChild(message.is_send ? messageContent : avatarTag);
|
messageElement.appendChild(message.is_send ? messageContent : avatarTag);
|
||||||
messageElement.appendChild(message.is_send ? avatarTag : messageContent);
|
messageElement.appendChild(message.is_send ? avatarTag : messageContent);
|
||||||
}
|
}
|
||||||
if (message.sub_type == 3) {
|
else if (message.sub_type == 3) {
|
||||||
// displayname 和 file
|
// displayname 和 file
|
||||||
messageContent.className = `content-wrapper content-wrapper-${side}`;
|
messageContent.className = `content-wrapper content-wrapper-${side}`;
|
||||||
if (message.is_chatroom && !message.is_send) {
|
if (message.is_chatroom && !message.is_send) {
|
||||||
@ -861,6 +919,19 @@ input {
|
|||||||
messageElement.appendChild(message.is_send ? messageContent : avatarTag);
|
messageElement.appendChild(message.is_send ? messageContent : avatarTag);
|
||||||
messageElement.appendChild(message.is_send ? avatarTag : messageContent);
|
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) {
|
else if (message.type == 34) {
|
||||||
|
@ -14,6 +14,7 @@ types = {
|
|||||||
'表情包': 47,
|
'表情包': 47,
|
||||||
'音乐与音频': 4903,
|
'音乐与音频': 4903,
|
||||||
'文件': 4906,
|
'文件': 4906,
|
||||||
|
'分享卡片':4905,
|
||||||
'拍一拍等系统消息': 10000
|
'拍一拍等系统消息': 10000
|
||||||
}
|
}
|
||||||
Stylesheet = """
|
Stylesheet = """
|
||||||
@ -34,7 +35,7 @@ class ExportDialog(QDialog):
|
|||||||
if file_type == 'html':
|
if file_type == 'html':
|
||||||
self.export_type = Output.HTML
|
self.export_type = Output.HTML
|
||||||
self.export_choices = {"文本": True, "图片": True, "语音": False, "视频": False, "表情包": False,
|
self.export_choices = {"文本": True, "图片": True, "语音": False, "视频": False, "表情包": False,
|
||||||
'音乐与音频': False,'文件': False,
|
'音乐与音频': False,'分享卡片':False,'文件': False,
|
||||||
'拍一拍等系统消息': True} # 定义导出的数据类型,默认全部选择
|
'拍一拍等系统消息': True} # 定义导出的数据类型,默认全部选择
|
||||||
elif file_type == 'csv':
|
elif file_type == 'csv':
|
||||||
self.export_type = Output.CSV
|
self.export_type = Output.CSV
|
||||||
|
@ -7,6 +7,7 @@ import requests
|
|||||||
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
|
||||||
|
|
||||||
|
|
||||||
def decompress_CompressContent(data):
|
def decompress_CompressContent(data):
|
||||||
@ -20,13 +21,15 @@ def decompress_CompressContent(data):
|
|||||||
try:
|
try:
|
||||||
dst = lz4.block.decompress(data, uncompressed_size=len(data) << 10)
|
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 :
|
except:
|
||||||
print("Decompression failed: potentially corrupt input or insufficient buffer size.")
|
print("Decompression failed: potentially corrupt input or insufficient buffer size.")
|
||||||
return ''
|
return ''
|
||||||
return decoded_string
|
return decoded_string
|
||||||
|
|
||||||
|
|
||||||
def escape_js_and_html(input_str):
|
def escape_js_and_html(input_str):
|
||||||
|
if not input_str:
|
||||||
|
return ''
|
||||||
# 转义HTML特殊字符
|
# 转义HTML特殊字符
|
||||||
html_escaped = html.escape(input_str, quote=False)
|
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):
|
def get_website_name(url):
|
||||||
parsed_url = urlparse(url)
|
parsed_url = urlparse(url)
|
||||||
domain = f"{parsed_url.scheme}://{parsed_url.netloc}"
|
domain = f"{parsed_url.scheme}://{parsed_url.netloc}"
|
||||||
@ -146,7 +183,7 @@ def get_website_name(url):
|
|||||||
website_name = soup.title.string.strip()
|
website_name = soup.title.string.strip()
|
||||||
index = website_name.find("-")
|
index = website_name.find("-")
|
||||||
if index != -1: # 如果找到了 "-"
|
if index != -1: # 如果找到了 "-"
|
||||||
website_name = website_name[index+1:].strip()
|
website_name = website_name[index + 1:].strip()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Get Website Info Error: {e}")
|
print(f"Get Website Info Error: {e}")
|
||||||
return website_name
|
return website_name
|
||||||
@ -162,7 +199,7 @@ def get_audio_url(url):
|
|||||||
elif response.status_code == 200:
|
elif response.status_code == 200:
|
||||||
print('音乐文件已失效,url:' + url)
|
print('音乐文件已失效,url:' + url)
|
||||||
else:
|
else:
|
||||||
print('音乐文件地址获取失败,url:' + url +',状态码' + str(response.status_code))
|
print('音乐文件地址获取失败,url:' + url + ',状态码' + str(response.status_code))
|
||||||
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
|
||||||
|
Loading…
Reference in New Issue
Block a user