feat: improve system message display in dashboard
This commit is contained in:
@@ -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("<sysmsg"):
|
||||
payload["summary"] = text
|
||||
return payload
|
||||
|
||||
try:
|
||||
root = ET.fromstring(text)
|
||||
except Exception:
|
||||
payload["summary"] = text
|
||||
return payload
|
||||
|
||||
payload["sysmsg_type"] = _safe_text(root.attrib.get("type")).strip()
|
||||
if payload["sysmsg_type"] == "revokemsg":
|
||||
revoke_node = root.find("revokemsg")
|
||||
if revoke_node is not None:
|
||||
payload["session"] = _safe_text(revoke_node.findtext("session")).strip()
|
||||
payload["msgid"] = _safe_text(revoke_node.findtext("msgid")).strip()
|
||||
payload["newmsgid"] = _safe_text(revoke_node.findtext("newmsgid")).strip()
|
||||
payload["replace_msg"] = _safe_text(revoke_node.findtext("replacemsg")).strip()
|
||||
payload["summary"] = payload["replace_msg"] or "撤回了一条消息"
|
||||
return payload
|
||||
|
||||
payload["summary"] = _safe_text(root.findtext(".//content")).strip() or text
|
||||
return payload
|
||||
|
||||
|
||||
def _compact_media_caption(content: str, fallback: str) -> 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 "",
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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("<sysmsg"):
|
||||
return payload
|
||||
|
||||
try:
|
||||
root = ET.fromstring(text)
|
||||
except Exception:
|
||||
payload["summary"] = text
|
||||
return payload
|
||||
|
||||
payload["sysmsg_type"] = str(root.attrib.get("type", "")).strip()
|
||||
if payload["sysmsg_type"] == "revokemsg":
|
||||
revoke_node = root.find("revokemsg")
|
||||
if revoke_node is not None:
|
||||
payload["session"] = str(revoke_node.findtext("session", "") or "").strip()
|
||||
payload["msgid"] = str(revoke_node.findtext("msgid", "") or "").strip()
|
||||
payload["newmsgid"] = str(revoke_node.findtext("newmsgid", "") or "").strip()
|
||||
payload["replace_msg"] = str(revoke_node.findtext("replacemsg", "") or "").strip()
|
||||
payload["summary"] = payload["replace_msg"] or "撤回了一条消息"
|
||||
return payload
|
||||
|
||||
payload["summary"] = str(root.findtext(".//content", "") or "").strip() or text
|
||||
return payload
|
||||
|
||||
|
||||
def _proxy_remote_media(target_url: str) -> 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("<sysmsg"):
|
||||
sysmsg_payload = _parse_sysmsg_payload(raw_content)
|
||||
msg['content'] = sysmsg_payload.get('summary') or '[系统消息]'
|
||||
msg['message_xml'] = raw_content
|
||||
msg['sysmsg_type'] = sysmsg_payload.get('sysmsg_type', '')
|
||||
msg['sysmsg_summary'] = sysmsg_payload.get('summary', '')
|
||||
msg['sysmsg_replace_msg'] = sysmsg_payload.get('replace_msg', '')
|
||||
msg['sysmsg_session'] = sysmsg_payload.get('session', '')
|
||||
msg['sysmsg_msgid'] = sysmsg_payload.get('msgid', '')
|
||||
msg['sysmsg_newmsgid'] = sysmsg_payload.get('newmsgid', '')
|
||||
continue
|
||||
|
||||
if _is_emoji_message(msg):
|
||||
msg['content'] = '[表情]'
|
||||
msg['emoji_preview_url'] = msg.get('image_path') or _extract_emoji_preview_url(msg.get('attachment_url', ''))
|
||||
|
||||
@@ -656,7 +656,13 @@
|
||||
<div v-if="msg.linkDescription" class="message-link-description" v-text="msg.linkDescription"></div>
|
||||
<div v-if="msg.linkUrl" class="message-link-url" v-text="msg.linkUrl"></div>
|
||||
</div>
|
||||
<div v-else class="message-system-text" v-text="msg.content"></div>
|
||||
<div v-else class="message-system-bubble">
|
||||
<div class="message-system-tags">
|
||||
<span class="message-system-tag">系统</span>
|
||||
<span v-if="msg.sysmsgType === 'revokemsg'" class="message-system-tag is-revoke">撤回</span>
|
||||
</div>
|
||||
<div class="message-system-text" v-text="msg.content"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% 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 {
|
||||
|
||||
@@ -102,6 +102,16 @@
|
||||
<img v-if="scope.row.message_thumb" :src="scope.row.message_thumb" class="message-thumb" @click="showVideo(scope.row)">
|
||||
</div>
|
||||
|
||||
<div v-else-if="isSystemXmlMessage(scope.row)" class="message-system-card">
|
||||
<div class="message-system-tags">
|
||||
<span class="message-type-badge">系统</span>
|
||||
<span v-if="scope.row.sysmsg_type === 'revokemsg'" class="message-type-badge is-revoke">撤回</span>
|
||||
</div>
|
||||
<div class="message-system-summary">
|
||||
{% raw %}{{ scope.row.sysmsg_summary || scope.row.content || '系统消息' }}{% endraw %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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' && hasQuotedPreview(scope.row.quoted_preview_image)" class="quoted-media-preview">
|
||||
@@ -147,6 +157,10 @@
|
||||
<el-descriptions-item label="发送者">{% raw %}{{ selectedMessage.sender_name }}{% endraw %}</el-descriptions-item>
|
||||
<el-descriptions-item label="消息类型">{% raw %}{{ getMessageTypeName(selectedMessage.message_type) }}{% endraw %}</el-descriptions-item>
|
||||
<el-descriptions-item label="内容">{% raw %}{{ selectedMessage.content }}{% endraw %}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="selectedMessage.sysmsg_type" label="系统类型">{% raw %}{{ selectedMessage.sysmsg_type }}{% endraw %}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="selectedMessage.sysmsg_replace_msg" label="系统摘要">{% raw %}{{ selectedMessage.sysmsg_replace_msg }}{% endraw %}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="selectedMessage.sysmsg_msgid" label="原消息ID">{% raw %}{{ selectedMessage.sysmsg_msgid }}{% endraw %}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="selectedMessage.sysmsg_newmsgid" label="新消息ID">{% raw %}{{ selectedMessage.sysmsg_newmsgid }}{% endraw %}</el-descriptions-item>
|
||||
|
||||
<el-descriptions-item v-if="selectedMessage.message_type == 3 || selectedMessage.message_type == 43 || isEmojiMessage(selectedMessage)" label="媒体内容">
|
||||
<img v-if="selectedMessage.message_type == 3 && selectedMessage.image_path" :src="getImageUrl(selectedMessage.image_path)" style="max-width: 100%; border-radius: 16px;">
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user