feat: preview emoji media in message list

This commit is contained in:
liuwei
2026-04-13 12:10:37 +08:00
parent ada1b656e0
commit 45d97b2989
2 changed files with 49 additions and 1 deletions

View File

@@ -1,4 +1,6 @@
import requests
import html
import re
from flask import Blueprint, render_template, jsonify, request, current_app, Response, stream_with_context
from .auth import login_required
from loguru import logger
@@ -31,6 +33,17 @@ def _is_usable_local_media_path(value: str) -> bool:
return False
def _extract_emoji_preview_url(xml_text: str) -> str:
if not xml_text:
return ''
patterns = (r'cdnurl="([^"]+)"', r'encrypturl="([^"]+)"', r'externurl="([^"]+)"')
for pattern in patterns:
match = re.search(pattern, xml_text)
if match:
return html.unescape(match.group(1))
return ''
def _proxy_remote_media(target_url: str) -> Response:
if not target_url:
return Response("missing url", status=400)
@@ -109,6 +122,7 @@ def get_messages():
if _is_emoji_message(msg):
msg['content'] = '[表情]'
msg['emoji_preview_url'] = msg.get('image_path') or _extract_emoji_preview_url(msg.get('attachment_url', ''))
continue
# 处理消息内容,格式化引用消息

View File

@@ -87,6 +87,16 @@
<img v-else-if="scope.row.message_thumb" :src="scope.row.message_thumb" class="message-thumb" @click="showImage(scope.row)">
</div>
<div v-else-if="isEmojiMessage(scope.row)" class="message-media-preview">
<div class="message-media-label">【表情消息】</div>
<img
v-if="getEmojiPreviewUrl(scope.row)"
:src="getEmojiPreviewUrl(scope.row)"
class="message-thumb"
@click="showEmoji(scope.row)">
<div v-else class="message-text-preview is-muted">【表情消息】等待下载完成</div>
</div>
<div v-else-if="scope.row.message_type == 43" class="message-media-preview">
<div class="message-media-label">【视频消息】</div>
<img v-if="scope.row.message_thumb" :src="scope.row.message_thumb" class="message-thumb" @click="showVideo(scope.row)">
@@ -138,8 +148,9 @@
<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.message_type == 3 || selectedMessage.message_type == 43" label="媒体内容">
<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;">
<img v-else-if="isEmojiMessage(selectedMessage) && getEmojiPreviewUrl(selectedMessage)" :src="getEmojiPreviewUrl(selectedMessage)" style="max-width: 100%; border-radius: 16px;">
<img v-else-if="selectedMessage.message_type == 3 && selectedMessage.message_thumb" :src="selectedMessage.message_thumb" style="max-width: 100%; border-radius: 16px;">
<video v-if="selectedMessage.message_type == 43 && selectedMessage.attachment_url" :src="selectedMessage.attachment_url" controls style="max-width: 100%; border-radius: 16px;"></video>
</el-descriptions-item>
@@ -153,6 +164,7 @@
<el-dialog :visible.sync="imageDialogVisible" append-to-body width="80%" class="image-dialog">
<img v-if="selectedMessage && selectedMessage.image_path" :src="getImageUrl(selectedMessage.image_path)" style="max-width: 100%; border-radius: 18px;">
<img v-else-if="selectedMessage && getEmojiPreviewUrl(selectedMessage)" :src="getEmojiPreviewUrl(selectedMessage)" style="max-width: 100%; border-radius: 18px;">
<img v-else-if="selectedMessage && selectedMessage.message_thumb" :src="selectedMessage.message_thumb" style="max-width: 100%; border-radius: 18px;">
</el-dialog>
</div>
@@ -321,6 +333,10 @@
this.selectedMessage = message;
this.imageDialogVisible = true;
},
showEmoji(message) {
this.selectedMessage = message;
this.imageDialogVisible = true;
},
showQuotedImage(url) {
const resolvedUrl = this.getQuotedPreviewUrl(url);
if (!resolvedUrl) return;
@@ -341,6 +357,10 @@
};
return typeMap[type] || `未知类型(${type})`;
},
isEmojiMessage(message) {
if (!message) return false;
return ['47', '1048625', '1090519089'].includes(String(message.message_type));
},
getImageUrl(imagePath) {
if (!imagePath) return '';
@@ -367,6 +387,20 @@
return `/static/images/${fileName}`;
},
getEmojiPreviewUrl(message) {
if (!message) return '';
if (message.image_path) {
return this.getImageUrl(message.image_path);
}
const previewUrl = message.emoji_preview_url || '';
if (!previewUrl) {
return '';
}
if (previewUrl.startsWith('http://') || previewUrl.startsWith('https://')) {
return this.getMediaProxyUrl(previewUrl);
}
return this.getImageUrl(previewUrl);
},
getMediaProxyUrl(url) {
if (!url) return '';
if (url.startsWith('/api/messages/media_proxy')) {