diff --git a/admin/dashboard/blueprints/contacts.py b/admin/dashboard/blueprints/contacts.py index a353780..df5b2b3 100644 --- a/admin/dashboard/blueprints/contacts.py +++ b/admin/dashboard/blueprints/contacts.py @@ -95,6 +95,50 @@ def _parse_app_message_payload(content: str): return payload +def _parse_sys_message_payload(content: str): + payload = { + "sysmsg_type": "", + "summary": "", + "replace_msg": "", + "session": "", + "msgid": "", + "newmsgid": "" + } + text = _safe_text(content).strip() + if not text.startswith(" str: + text = _safe_text(content).strip() + if not text: + return fallback + if text.startswith("<"): + return fallback + return text + + def _normalize_recent_message(server, raw_message: dict, chat_type: str, target_wxid: str): sender = _safe_text(raw_message.get("sender")).strip() message_type = str(raw_message.get("message_type", "")) @@ -117,16 +161,16 @@ def _normalize_recent_message(server, raw_message: dict, chat_type: str, target_ if message_type == "3": display_type = "image" - display_content = content or "[图片]" + display_content = _compact_media_caption(content, "[图片]") elif message_type in {"47", "1048625", "1090519089"}: display_type = "image" if media_url else "text" - display_content = content or "[表情]" + display_content = _compact_media_caption(content, "[表情]") elif message_type == "34": display_type = "voice" - display_content = content or "[语音]" + display_content = _compact_media_caption(content, "[语音]") elif message_type == "43": display_type = "video" - display_content = content or "[视频]" + display_content = _compact_media_caption(content, "[视频]") elif message_type == "49": app_payload = _parse_app_message_payload(content) if app_payload.get("url") or app_payload.get("title"): @@ -138,7 +182,9 @@ def _normalize_recent_message(server, raw_message: dict, chat_type: str, target_ display_content = app_payload.get("description") or content or "[应用消息]" elif message_type in {"10000", "10002"}: display_type = "system" - display_content = content or "[系统消息]" + sys_payload = _parse_sys_message_payload(content) + display_content = sys_payload.get("summary") or content or "[系统消息]" + link_payload = sys_payload return { "timestamp": _safe_text(raw_message.get("timestamp")), @@ -154,7 +200,8 @@ def _normalize_recent_message(server, raw_message: dict, chat_type: str, target_ "message_thumb": message_thumb, "message_id": raw_message.get("message_id"), "link_payload": link_payload, - "is_self": bool(self_wxid and sender == self_wxid) + "is_self": bool(self_wxid and sender == self_wxid), + "sysmsg_type": (link_payload or {}).get("sysmsg_type", "") if display_type == "system" else "", } diff --git a/admin/dashboard/blueprints/messages.py b/admin/dashboard/blueprints/messages.py index 2a48b61..f978044 100644 --- a/admin/dashboard/blueprints/messages.py +++ b/admin/dashboard/blueprints/messages.py @@ -48,6 +48,40 @@ def _extract_emoji_preview_url(xml_text: str) -> str: return '' +def _parse_sysmsg_payload(content: str) -> dict: + payload = { + "sysmsg_type": "", + "summary": "", + "replace_msg": "", + "session": "", + "msgid": "", + "newmsgid": "", + } + text = str(content or "").strip() + if not text.startswith(" Response: if not target_url: return Response("missing url", status=400) @@ -118,12 +152,25 @@ def get_messages(): # 处理消息数据,添加群组名称和发送者昵称,并格式化引用消息 for msg in result['messages']: + raw_content = str(msg.get('content') or '') # 获取群组名称 msg['group_name'] = server.contact_manager.get_nickname(msg['group_id']) or msg['group_id'] # 获取发送者昵称 msg['sender_name'] = server.contact_manager.get_group_name(msg['group_id'], msg['sender']) or msg['sender'] + if raw_content.startswith(" -
+
+
+ 系统 + 撤回 +
+
+
{% endraw %} @@ -1177,6 +1183,7 @@ rawContent: item.content || '', time: item.timestamp || '', isSelf: !!item.is_self, + sysmsgType: item.sysmsg_type || '', mediaUrl: this.getChatMediaUrl(item.media_url || item.image_path || item.attachment_url || item.message_thumb || ''), linkTitle: linkPayload.title || '', linkDescription: linkPayload.description || '', @@ -1458,6 +1465,14 @@ max-width: 90%; background: rgba(241,245,249,0.92); color: #475569; border-style: dashed; text-align: center; box-shadow: none; } + .message-system-bubble { display: flex; flex-direction: column; align-items: center; gap: 8px; } + .message-system-tags { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; justify-content: center; } + .message-system-tag { + display: inline-flex; align-items: center; justify-content: center; min-height: 24px; padding: 0 10px; + border-radius: 999px; font-size: 12px; font-weight: 700; color: #475569; + background: rgba(255,255,255,0.76); border: 1px solid rgba(148,163,184,0.18); + } + .message-system-tag.is-revoke { color: #9f1239; background: rgba(255,241,242,0.92); border-color: rgba(244,114,182,0.2); } .message-text, .message-system-text { white-space: pre-wrap; word-break: break-word; line-height: 1.7; } .message-media { display: flex; flex-direction: column; gap: 10px; } .message-image, .message-video { diff --git a/admin/dashboard/templates/message_list.html b/admin/dashboard/templates/message_list.html index 94bfa96..1e3e3b3 100644 --- a/admin/dashboard/templates/message_list.html +++ b/admin/dashboard/templates/message_list.html @@ -102,6 +102,16 @@ +
+
+ 系统 + 撤回 +
+
+ {% raw %}{{ scope.row.sysmsg_summary || scope.row.content || '系统消息' }}{% endraw %} +
+
+
{% raw %}{{ scope.row.content || `【消息类型: ${scope.row.message_type}】` }}{% endraw %}
@@ -147,6 +157,10 @@ {% raw %}{{ selectedMessage.sender_name }}{% endraw %} {% raw %}{{ getMessageTypeName(selectedMessage.message_type) }}{% endraw %} {% raw %}{{ selectedMessage.content }}{% endraw %} + {% raw %}{{ selectedMessage.sysmsg_type }}{% endraw %} + {% raw %}{{ selectedMessage.sysmsg_replace_msg }}{% endraw %} + {% raw %}{{ selectedMessage.sysmsg_msgid }}{% endraw %} + {% raw %}{{ selectedMessage.sysmsg_newmsgid }}{% endraw %} @@ -353,10 +367,15 @@ 3: '图片消息', 47: '表情消息', 43: '视频消息', - 49: '链接消息' + 49: '链接消息', + 10000: '系统消息', + 10002: '系统消息' }; return typeMap[type] || `未知类型(${type})`; }, + isSystemXmlMessage(message) { + return !!(message && (message.sysmsg_type || '').length); + }, isEmojiMessage(message) { if (!message) return false; return ['47', '1048625', '1090519089'].includes(String(message.message_type)); @@ -529,6 +548,49 @@ gap: 8px; } + .message-system-card { + display: flex; + flex-direction: column; + gap: 8px; + padding: 10px 12px; + border-radius: 14px; + background: linear-gradient(135deg, rgba(148,163,184,0.12), rgba(226,232,240,0.45)); + border: 1px solid rgba(148,163,184,0.18); + } + + .message-system-tags { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; + } + + .message-type-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 24px; + padding: 0 10px; + border-radius: 999px; + font-size: 12px; + font-weight: 700; + color: #475569; + background: rgba(255,255,255,0.72); + border: 1px solid rgba(148,163,184,0.2); + } + + .message-type-badge.is-revoke { + color: #9f1239; + background: rgba(255,241,242,0.92); + border-color: rgba(244,114,182,0.2); + } + + .message-system-summary { + color: #334155; + line-height: 1.6; + word-break: break-word; + } + .quoted-media-preview { margin-top: 8px; display: flex;