feat: improve quoted message rendering in dashboard
This commit is contained in:
@@ -3,7 +3,7 @@ from .auth import login_required
|
||||
from loguru import logger
|
||||
import xml.etree.ElementTree as ET
|
||||
from datetime import datetime
|
||||
from utils.message_formatter import format_quote_message
|
||||
from utils.message_formatter import format_quote_message, parse_quote_message
|
||||
|
||||
# 创建消息管理蓝图
|
||||
messages_bp = Blueprint('messages', __name__)
|
||||
@@ -68,7 +68,11 @@ def get_messages():
|
||||
# 检查是否为引用消息
|
||||
if '<refermsg>' in msg['content']:
|
||||
# 使用格式化工具处理引用消息
|
||||
msg['content'] = format_quote_message(msg['content'])
|
||||
quote_data = parse_quote_message(msg['content'])
|
||||
msg['content'] = quote_data['formatted_message']
|
||||
msg['quoted_type'] = quote_data['reference_type']
|
||||
msg['quoted_preview_image'] = quote_data['preview_image']
|
||||
msg['quoted_preview_video_thumb'] = quote_data['preview_video_thumb']
|
||||
else:
|
||||
# 其他类型的应用消息,解析 XML 提取标题
|
||||
root = ET.fromstring(msg['content'])
|
||||
|
||||
@@ -94,6 +94,14 @@
|
||||
|
||||
<div v-else class="message-text-preview is-muted">
|
||||
{% raw %}{{ scope.row.content || `【消息类型: ${scope.row.message_type}】` }}{% endraw %}
|
||||
<div v-if="scope.row.quoted_type === 'image' && scope.row.quoted_preview_image" class="quoted-media-preview">
|
||||
<div class="message-media-label">【引用图片】</div>
|
||||
<img :src="scope.row.quoted_preview_image" class="message-thumb" @click="showQuotedImage(scope.row.quoted_preview_image)">
|
||||
</div>
|
||||
<div v-else-if="scope.row.quoted_type === 'video' && scope.row.quoted_preview_video_thumb" class="quoted-media-preview">
|
||||
<div class="message-media-label">【引用视频】</div>
|
||||
<img :src="scope.row.quoted_preview_video_thumb" class="message-thumb">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -198,7 +206,8 @@
|
||||
},
|
||||
detailDialogVisible: false,
|
||||
imageDialogVisible: false,
|
||||
selectedMessage: null
|
||||
selectedMessage: null,
|
||||
quotedPreviewUrl: ''
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
@@ -309,6 +318,10 @@
|
||||
this.selectedMessage = message;
|
||||
this.imageDialogVisible = true;
|
||||
},
|
||||
showQuotedImage(url) {
|
||||
this.selectedMessage = { image_path: '', message_thumb: url };
|
||||
this.imageDialogVisible = true;
|
||||
},
|
||||
showVideo(message) {
|
||||
this.selectedMessage = message;
|
||||
this.detailDialogVisible = true;
|
||||
@@ -444,6 +457,13 @@
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.quoted-media-preview {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.message-media-label {
|
||||
font-size: 12px;
|
||||
color: #64748b;
|
||||
|
||||
@@ -3,6 +3,93 @@ import html
|
||||
import re
|
||||
|
||||
|
||||
def _clean_text(value: str) -> str:
|
||||
if not value:
|
||||
return ""
|
||||
value = html.unescape(value)
|
||||
value = re.sub(r"<br\s*/?>", "\n", value, flags=re.IGNORECASE)
|
||||
value = re.sub(r"<[^>]+>", "", value)
|
||||
return value.strip()
|
||||
|
||||
|
||||
def _extract_first(pattern: str, text: str, default: str = "") -> str:
|
||||
match = re.search(pattern, text, re.DOTALL | re.IGNORECASE)
|
||||
return match.group(1) if match else default
|
||||
|
||||
|
||||
def _format_referenced_content(ref_type: str, quoted_content: str, xml_content: str) -> str:
|
||||
cleaned = _clean_text(quoted_content)
|
||||
lower_xml = (quoted_content or "") + (xml_content or "")
|
||||
lower_xml = lower_xml.lower()
|
||||
|
||||
if ref_type in {"3"} or "<img" in lower_xml or "cdnthumburl" in lower_xml:
|
||||
return "[图片]"
|
||||
if ref_type in {"43", "62"} or "<videomsg" in lower_xml or "cdnvideourl" in lower_xml:
|
||||
return "[视频]"
|
||||
if ref_type in {"47", "1048625", "1090519089"} or "<emoji" in lower_xml or "<emoticonmd5>" in lower_xml:
|
||||
return "[表情]"
|
||||
if ref_type in {"34"} or "<voicemsg" in lower_xml:
|
||||
return "[语音]"
|
||||
if ref_type in {"48"} or "<location" in lower_xml:
|
||||
return "[位置]"
|
||||
if ref_type in {"49"}:
|
||||
title = _extract_first(r"<title>(.*?)</title>", quoted_content) or _extract_first(r"<title>(.*?)</title>", xml_content)
|
||||
title = _clean_text(title)
|
||||
return f"[链接] {title}" if title else "[链接]"
|
||||
|
||||
if cleaned:
|
||||
return cleaned
|
||||
return "[消息]"
|
||||
|
||||
|
||||
def _extract_media_preview(ref_type: str, quoted_content: str) -> dict:
|
||||
payload = html.unescape(quoted_content or "")
|
||||
preview = {"reference_type": "text", "preview_image": "", "preview_video_thumb": ""}
|
||||
|
||||
if ref_type in {"3"} or "<img" in payload.lower():
|
||||
preview["reference_type"] = "image"
|
||||
preview["preview_image"] = (
|
||||
_extract_first(r'cdnthumburl="(.*?)"', payload)
|
||||
or _extract_first(r"<cdnthumburl><!\[CDATA\[(.*?)\]\]></cdnthumburl>", payload)
|
||||
or _extract_first(r"<cdnmidimgurl><!\[CDATA\[(.*?)\]\]></cdnmidimgurl>", payload)
|
||||
)
|
||||
return preview
|
||||
|
||||
if ref_type in {"43", "62"} or "<videomsg" in payload.lower():
|
||||
preview["reference_type"] = "video"
|
||||
preview["preview_video_thumb"] = (
|
||||
_extract_first(r'cdnthumburl="(.*?)"', payload)
|
||||
or _extract_first(r"<cdnthumburl><!\[CDATA\[(.*?)\]\]></cdnthumburl>", payload)
|
||||
)
|
||||
return preview
|
||||
|
||||
if ref_type in {"47", "1048625", "1090519089"} or "<emoji" in payload.lower():
|
||||
preview["reference_type"] = "emoji"
|
||||
return preview
|
||||
|
||||
return preview
|
||||
|
||||
|
||||
def parse_quote_message(xml_content: str) -> dict:
|
||||
xml_content = xml_content.replace('<', '<').replace('>', '>')
|
||||
main_content = _clean_text(_extract_first(r'<title>(.*?)</title>', xml_content, "[无标题]")) or "[无标题]"
|
||||
display_name = _clean_text(_extract_first(r'<displayname>(.*?)</displayname>', xml_content, "未知用户")) or "未知用户"
|
||||
quoted_content = _extract_first(r'<refermsg>.*?<content>(.*?)</content>', xml_content)
|
||||
ref_type = _extract_first(r'<refermsg>.*?<type>(.*?)</type>', xml_content)
|
||||
pretty_reference = _format_referenced_content(ref_type, quoted_content, xml_content)
|
||||
media_preview = _extract_media_preview(ref_type, quoted_content)
|
||||
|
||||
return {
|
||||
"main_content": main_content,
|
||||
"display_name": display_name,
|
||||
"quoted_content": pretty_reference,
|
||||
"reference_type": media_preview.get("reference_type", "text"),
|
||||
"preview_image": media_preview.get("preview_image", ""),
|
||||
"preview_video_thumb": media_preview.get("preview_video_thumb", ""),
|
||||
"formatted_message": f"{main_content}\n引用 {display_name}:{pretty_reference}" if display_name and pretty_reference else main_content
|
||||
}
|
||||
|
||||
|
||||
def format_quote_message(xml_content):
|
||||
"""
|
||||
格式化引用消息
|
||||
@@ -14,31 +101,7 @@ def format_quote_message(xml_content):
|
||||
格式化后的消息文本
|
||||
"""
|
||||
try:
|
||||
|
||||
xml_content = xml_content.replace('<', '<').replace('>', '>')
|
||||
# 使用正则表达式直接提取关键信息,避免XML解析问题
|
||||
title_match = re.search(r'<title>(.*?)</title>', xml_content)
|
||||
main_content = title_match.group(1) if title_match else "[无标题]"
|
||||
|
||||
# 提取引用消息的发送者和内容
|
||||
display_name_match = re.search(r'<displayname>(.*?)</displayname>', xml_content)
|
||||
display_name = display_name_match.group(1) if display_name_match else "未知用户"
|
||||
|
||||
quoted_content_match = re.search(r'<refermsg>.*?<content>(.*?)</content>', xml_content, re.DOTALL)
|
||||
quoted_content = quoted_content_match.group(1) if quoted_content_match else ""
|
||||
|
||||
# 解码HTML实体
|
||||
try:
|
||||
quoted_content = html.unescape(quoted_content)
|
||||
except:
|
||||
pass # 如果解码失败,使用原始内容
|
||||
|
||||
# 构建格式化的引用消息
|
||||
if display_name and quoted_content:
|
||||
formatted_message = f"{main_content}\n引用 {display_name}:{quoted_content}"
|
||||
return formatted_message
|
||||
|
||||
return main_content
|
||||
return parse_quote_message(xml_content)["formatted_message"]
|
||||
except Exception as e:
|
||||
# 如果解析失败,尝试提取title标签内容
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user