This commit is contained in:
cc
2026-05-06 00:05:11 +08:00
parent 3f908a4dd3
commit abde85a900
9 changed files with 799 additions and 642 deletions

View File

@@ -2530,7 +2530,7 @@ class ChatService {
const rawRows = result.messages as Record<string, any>[]
const hasMore = rawRows.length > pageLimit
const selectedRows = hasMore ? rawRows.slice(0, pageLimit) : rawRows
const mapped = this.mapRowsToMessages(selectedRows)
const mapped = this.mapRowsToMessages(selectedRows, sessionId)
const visible = mapped.filter((msg) => this.isMessageVisibleForSession(sessionId, msg))
const outputMessages = (visible.length === 0 && mapped.length > 0)
? mapped
@@ -2541,6 +2541,7 @@ class ChatService {
const normalized = this.normalizeMessageOrder(outputMessages)
if (normalized.length > 0) {
await this.repairEmojiMessages(normalized)
await this.resolveQuotedMessages(normalized, sessionId)
}
return {
@@ -2597,7 +2598,7 @@ class ChatService {
}
// 转换为 Message 对象
const messages = this.mapRowsToMessages(res.messages as Record<string, any>[])
const messages = this.mapRowsToMessages(res.messages as Record<string, any>[], sessionId)
const normalized = this.normalizeMessageOrder(messages)
// 并发检查并修复缺失 CDN URL 的表情包
@@ -2834,7 +2835,7 @@ class ChatService {
const rowsToProcess = queuedRows
queuedRows = []
const mappedMessages = this.mapRowsToMessages(rowsToProcess)
const mappedMessages = this.mapRowsToMessages(rowsToProcess, sessionId)
for (let index = 0; index < mappedMessages.length; index += 1) {
const msg = mappedMessages[index]
rawRowsConsumed += 1
@@ -4825,8 +4826,8 @@ class ChatService {
/**
* HTTP API 复用消息解析逻辑,确保和应用内展示一致。
*/
mapRowsToMessagesForApi(rows: Record<string, any>[]): Message[] {
return this.mapRowsToMessages(rows)
mapRowsToMessagesForApi(rows: Record<string, any>[], sessionId: string): Message[] {
return this.mapRowsToMessages(rows, sessionId)
}
mapRowsToMessagesLiteForApi(rows: Record<string, any>[]): Message[] {
@@ -4880,7 +4881,7 @@ class ChatService {
return messages
}
private mapRowsToMessages(rows: Record<string, any>[]): Message[] {
private mapRowsToMessages(rows: Record<string, any>[], sessionId: string): Message[] {
const myWxid = this.configService.get('myWxid')
const messages: Message[] = []
@@ -5000,11 +5001,23 @@ class ChatService {
encrypVer = imageInfo.encrypVer
cdnThumbUrl = imageInfo.cdnThumbUrl
imageDatName = this.parseImageDatNameFromRow(row)
// 解析图片消息中的引用信息
const quoteInfo = this.parseMediaQuoteMessage(content, sessionId)
if (quoteInfo.content) quotedContent = quoteInfo.content
if (quoteInfo.sender) quotedSender = quoteInfo.sender
} else if (localType === 43) {
// 视频消息:优先从 packed_info_data 提取真实文件名32位十六进制再回退 XML
videoMd5 = this.parseVideoFileNameFromRow(row, content)
// 解析视频消息中的引用信息
const quoteInfo = this.parseMediaQuoteMessage(content, sessionId)
if (quoteInfo.content) quotedContent = quoteInfo.content
if (quoteInfo.sender) quotedSender = quoteInfo.sender
} else if (localType === 34 && content) {
voiceDurationSeconds = this.parseVoiceDurationSeconds(content)
// 解析语音消息中的引用信息
const quoteInfo = this.parseMediaQuoteMessage(content, sessionId)
if (quoteInfo.content) quotedContent = quoteInfo.content
if (quoteInfo.sender) quotedSender = quoteInfo.sender
} else if (localType === 42 && content) {
// 名片消息
const cardInfo = this.parseCardInfo(content)
@@ -5760,6 +5773,116 @@ class ChatService {
}
}
/**
* 解析媒体消息(图片/视频/语音)中的引用信息
* 这些消息的引用信息在 <extcommoninfo><refermsg> 中
*/
private parseMediaQuoteMessage(content: string, sessionId: string): { content?: string; sender?: string } {
try {
const normalizedContent = this.decodeHtmlEntities(content || '')
const referMsgStart = normalizedContent.indexOf('<refermsg>')
const referMsgEnd = normalizedContent.indexOf('</refermsg>')
if (referMsgStart === -1 || referMsgEnd === -1) {
return {}
}
const referMsgXml = normalizedContent.substring(referMsgStart, referMsgEnd + 11)
const svrid = this.extractXmlValue(referMsgXml, 'svrid')
console.log('[DEBUG] parseMediaQuoteMessage - svrid:', svrid)
if (!svrid) {
return {}
}
// 简化方案:返回 svrid 标记
console.log('[DEBUG] parseMediaQuoteMessage - 返回标记:', `__SVRID__${svrid}__`)
return { content: `__SVRID__${svrid}__` }
} catch {
return {}
}
}
async resolveQuotedMessages(messages: Message[], sessionId: string): Promise<void> {
console.log('[DEBUG] resolveQuotedMessages - 开始解析,消息数量:', messages.length)
const svridsToResolve: Array<{ msg: Message; svrid: string }> = []
for (const msg of messages) {
if (msg.quotedContent && msg.quotedContent.startsWith('__SVRID__')) {
const match = msg.quotedContent.match(/__SVRID__(.+?)__/)
if (match) {
console.log('[DEBUG] resolveQuotedMessages - 找到需要解析的svrid:', match[1])
svridsToResolve.push({ msg, svrid: match[1] })
}
}
}
console.log('[DEBUG] resolveQuotedMessages - 需要解析的数量:', svridsToResolve.length)
if (svridsToResolve.length === 0) return
const results = await Promise.allSettled(
svridsToResolve.map(({ svrid }) => {
console.log('[DEBUG] resolveQuotedMessages - 查询svrid:', svrid, 'sessionId:', sessionId)
return wcdbService.getMessageByServerId(sessionId, svrid)
})
)
console.log('[DEBUG] resolveQuotedMessages - 查询结果数量:', results.length)
for (let i = 0; i < results.length; i++) {
const result = results[i]
const { msg, svrid } = svridsToResolve[i]
console.log('[DEBUG] resolveQuotedMessages - 处理结果', i, ':', {
status: result.status,
success: result.status === 'fulfilled' ? result.value.success : false,
hasRow: result.status === 'fulfilled' && result.value.row ? true : false,
error: result.status === 'fulfilled' ? result.value.error : undefined,
svrid
})
if (result.status === 'fulfilled' && result.value.success && result.value.row) {
const localType = parseInt(result.value.row.local_type || '0', 10)
const rawMessageContent = result.value.row.message_content
const rawCompressContent = result.value.row.compress_content
console.log('[DEBUG] resolveQuotedMessages - 原始数据:', {
hasMessageContent: !!rawMessageContent,
hasCompressContent: !!rawCompressContent,
messageContentType: typeof rawMessageContent,
messageContentLength: rawMessageContent ? rawMessageContent.length : 0
})
const content = this.decodeMessageContent(rawMessageContent, rawCompressContent)
console.log('[DEBUG] resolveQuotedMessages - 解码后:', { localType, contentLength: content.length, contentPreview: content.substring(0, 50) })
if (localType === 1) {
msg.quotedContent = this.sanitizeQuotedContent(content)
} else if (localType === 3) {
msg.quotedContent = '[图片]'
} else if (localType === 34) {
msg.quotedContent = '[语音]'
} else if (localType === 43) {
msg.quotedContent = '[视频]'
} else if (localType === 47) {
msg.quotedContent = '[动画表情]'
} else if (localType === 49) {
msg.quotedContent = '[链接]'
} else {
msg.quotedContent = '[消息]'
}
console.log('[DEBUG] resolveQuotedMessages - 更新后的quotedContent:', msg.quotedContent)
} else {
msg.quotedContent = '[引用消息]'
console.log('[DEBUG] resolveQuotedMessages - 查询失败,使用占位符')
}
}
console.log('[DEBUG] resolveQuotedMessages - 完成')
}
private extractPreferredQuotedText(referMsgXml: string): string {
if (!referMsgXml) return ''
@@ -8792,7 +8915,7 @@ class ChatService {
return { success: false, error: result.error || '查询语音消息失败' }
}
let allVoiceMessages: Message[] = this.mapRowsToMessages(result.rows as Record<string, any>[])
let allVoiceMessages: Message[] = this.mapRowsToMessages(result.rows as Record<string, any>[], sessionId)
// 按 createTime 降序排序
allVoiceMessages.sort((a, b) => b.createTime - a.createTime)
@@ -8835,7 +8958,7 @@ class ChatService {
return { success: false, error: result.error || '查询图片消息失败' }
}
const mapped = this.mapRowsToMessages(result.rows as Record<string, any>[])
const mapped = this.mapRowsToMessages(result.rows as Record<string, any>[], sessionId)
let allImages: Array<{ imageMd5?: string; imageDatName?: string; createTime?: number }> = mapped
.filter(msg => msg.localType === 3)
.map(msg => ({
@@ -8960,7 +9083,7 @@ class ChatService {
if (!result.success || !Array.isArray(result.rows) || result.rows.length === 0) continue
if (result.rows.length >= perTypeFetch) maybeHasMore = true
const mapped = this.mapRowsToMessages(result.rows as Record<string, any>[])
const mapped = this.mapRowsToMessages(result.rows as Record<string, any>[], sessionId)
for (const message of mapped) {
const resourceType = this.resolveResourceType(message)
if (!resourceType || !typeSet.has(resourceType)) continue