diff --git a/admin/dashboard/blueprints/messages.py b/admin/dashboard/blueprints/messages.py index 7bba305..ec023cf 100644 --- a/admin/dashboard/blueprints/messages.py +++ b/admin/dashboard/blueprints/messages.py @@ -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 '' 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']) diff --git a/admin/dashboard/templates/message_list.html b/admin/dashboard/templates/message_list.html index 59c0a4e..915678e 100644 --- a/admin/dashboard/templates/message_list.html +++ b/admin/dashboard/templates/message_list.html @@ -94,6 +94,14 @@
{% raw %}{{ scope.row.content || `【消息类型: ${scope.row.message_type}】` }}{% endraw %} +
+
【引用图片】
+ +
+
+
【引用视频】
+ +
@@ -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; diff --git a/utils/message_formatter.py b/utils/message_formatter.py index 40c846b..28c29f1 100644 --- a/utils/message_formatter.py +++ b/utils/message_formatter.py @@ -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"", "\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 "" in lower_xml: + return "[表情]" + if ref_type in {"34"} or "(.*?)", quoted_content) or _extract_first(r"(.*?)", 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 "", payload) + or _extract_first(r"", payload) + ) + return preview + + if ref_type in {"43", "62"} or "", payload) + ) + return preview + + if ref_type in {"47", "1048625", "1090519089"} or " dict: + xml_content = xml_content.replace('<', '<').replace('>', '>') + main_content = _clean_text(_extract_first(r'(.*?)', xml_content, "[无标题]")) or "[无标题]" + display_name = _clean_text(_extract_first(r'(.*?)', xml_content, "未知用户")) or "未知用户" + quoted_content = _extract_first(r'.*?(.*?)', xml_content) + ref_type = _extract_first(r'.*?(.*?)', 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'(.*?)', xml_content) - main_content = title_match.group(1) if title_match else "[无标题]" - - # 提取引用消息的发送者和内容 - display_name_match = re.search(r'(.*?)', xml_content) - display_name = display_name_match.group(1) if display_name_match else "未知用户" - - quoted_content_match = re.search(r'.*?(.*?)', 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: