mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-05-06 07:26:48 +00:00
This commit is contained in:
@@ -2530,7 +2530,7 @@ class ChatService {
|
|||||||
const rawRows = result.messages as Record<string, any>[]
|
const rawRows = result.messages as Record<string, any>[]
|
||||||
const hasMore = rawRows.length > pageLimit
|
const hasMore = rawRows.length > pageLimit
|
||||||
const selectedRows = hasMore ? rawRows.slice(0, pageLimit) : rawRows
|
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 visible = mapped.filter((msg) => this.isMessageVisibleForSession(sessionId, msg))
|
||||||
const outputMessages = (visible.length === 0 && mapped.length > 0)
|
const outputMessages = (visible.length === 0 && mapped.length > 0)
|
||||||
? mapped
|
? mapped
|
||||||
@@ -2541,6 +2541,7 @@ class ChatService {
|
|||||||
const normalized = this.normalizeMessageOrder(outputMessages)
|
const normalized = this.normalizeMessageOrder(outputMessages)
|
||||||
if (normalized.length > 0) {
|
if (normalized.length > 0) {
|
||||||
await this.repairEmojiMessages(normalized)
|
await this.repairEmojiMessages(normalized)
|
||||||
|
await this.resolveQuotedMessages(normalized, sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -2597,7 +2598,7 @@ class ChatService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 转换为 Message 对象
|
// 转换为 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)
|
const normalized = this.normalizeMessageOrder(messages)
|
||||||
|
|
||||||
// 并发检查并修复缺失 CDN URL 的表情包
|
// 并发检查并修复缺失 CDN URL 的表情包
|
||||||
@@ -2834,7 +2835,7 @@ class ChatService {
|
|||||||
|
|
||||||
const rowsToProcess = queuedRows
|
const rowsToProcess = queuedRows
|
||||||
queuedRows = []
|
queuedRows = []
|
||||||
const mappedMessages = this.mapRowsToMessages(rowsToProcess)
|
const mappedMessages = this.mapRowsToMessages(rowsToProcess, sessionId)
|
||||||
for (let index = 0; index < mappedMessages.length; index += 1) {
|
for (let index = 0; index < mappedMessages.length; index += 1) {
|
||||||
const msg = mappedMessages[index]
|
const msg = mappedMessages[index]
|
||||||
rawRowsConsumed += 1
|
rawRowsConsumed += 1
|
||||||
@@ -4825,8 +4826,8 @@ class ChatService {
|
|||||||
/**
|
/**
|
||||||
* HTTP API 复用消息解析逻辑,确保和应用内展示一致。
|
* HTTP API 复用消息解析逻辑,确保和应用内展示一致。
|
||||||
*/
|
*/
|
||||||
mapRowsToMessagesForApi(rows: Record<string, any>[]): Message[] {
|
mapRowsToMessagesForApi(rows: Record<string, any>[], sessionId: string): Message[] {
|
||||||
return this.mapRowsToMessages(rows)
|
return this.mapRowsToMessages(rows, sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
mapRowsToMessagesLiteForApi(rows: Record<string, any>[]): Message[] {
|
mapRowsToMessagesLiteForApi(rows: Record<string, any>[]): Message[] {
|
||||||
@@ -4880,7 +4881,7 @@ class ChatService {
|
|||||||
return messages
|
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 myWxid = this.configService.get('myWxid')
|
||||||
|
|
||||||
const messages: Message[] = []
|
const messages: Message[] = []
|
||||||
@@ -5000,11 +5001,23 @@ class ChatService {
|
|||||||
encrypVer = imageInfo.encrypVer
|
encrypVer = imageInfo.encrypVer
|
||||||
cdnThumbUrl = imageInfo.cdnThumbUrl
|
cdnThumbUrl = imageInfo.cdnThumbUrl
|
||||||
imageDatName = this.parseImageDatNameFromRow(row)
|
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) {
|
} else if (localType === 43) {
|
||||||
// 视频消息:优先从 packed_info_data 提取真实文件名(32位十六进制),再回退 XML
|
// 视频消息:优先从 packed_info_data 提取真实文件名(32位十六进制),再回退 XML
|
||||||
videoMd5 = this.parseVideoFileNameFromRow(row, content)
|
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) {
|
} else if (localType === 34 && content) {
|
||||||
voiceDurationSeconds = this.parseVoiceDurationSeconds(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) {
|
} else if (localType === 42 && content) {
|
||||||
// 名片消息
|
// 名片消息
|
||||||
const cardInfo = this.parseCardInfo(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 {
|
private extractPreferredQuotedText(referMsgXml: string): string {
|
||||||
if (!referMsgXml) return ''
|
if (!referMsgXml) return ''
|
||||||
|
|
||||||
@@ -8792,7 +8915,7 @@ class ChatService {
|
|||||||
return { success: false, error: result.error || '查询语音消息失败' }
|
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 降序排序
|
// 按 createTime 降序排序
|
||||||
allVoiceMessages.sort((a, b) => b.createTime - a.createTime)
|
allVoiceMessages.sort((a, b) => b.createTime - a.createTime)
|
||||||
@@ -8835,7 +8958,7 @@ class ChatService {
|
|||||||
return { success: false, error: result.error || '查询图片消息失败' }
|
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
|
let allImages: Array<{ imageMd5?: string; imageDatName?: string; createTime?: number }> = mapped
|
||||||
.filter(msg => msg.localType === 3)
|
.filter(msg => msg.localType === 3)
|
||||||
.map(msg => ({
|
.map(msg => ({
|
||||||
@@ -8960,7 +9083,7 @@ class ChatService {
|
|||||||
if (!result.success || !Array.isArray(result.rows) || result.rows.length === 0) continue
|
if (!result.success || !Array.isArray(result.rows) || result.rows.length === 0) continue
|
||||||
if (result.rows.length >= perTypeFetch) maybeHasMore = true
|
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) {
|
for (const message of mapped) {
|
||||||
const resourceType = this.resolveResourceType(message)
|
const resourceType = this.resolveResourceType(message)
|
||||||
if (!resourceType || !typeSet.has(resourceType)) continue
|
if (!resourceType || !typeSet.has(resourceType)) continue
|
||||||
|
|||||||
@@ -3536,7 +3536,49 @@ class ExportService {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseQuoteMessage(content: string): { content?: string; sender?: string; type?: string } {
|
private async resolveQuotedMessagesForExport(messages: any[], sessionId: string): Promise<void> {
|
||||||
|
const svridsToResolve: Array<{ msg: any; svrid: string }> = []
|
||||||
|
|
||||||
|
for (const msg of messages) {
|
||||||
|
if (msg.replyToMessageId && msg.quotedContent === '[消息]') {
|
||||||
|
svridsToResolve.push({ msg, svrid: msg.replyToMessageId })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (svridsToResolve.length === 0) return
|
||||||
|
|
||||||
|
const results = await Promise.allSettled(
|
||||||
|
svridsToResolve.map(({ svrid }) => wcdbService.getMessageByServerId(sessionId, svrid))
|
||||||
|
)
|
||||||
|
|
||||||
|
for (let i = 0; i < results.length; i++) {
|
||||||
|
const result = results[i]
|
||||||
|
const { msg } = svridsToResolve[i]
|
||||||
|
|
||||||
|
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
|
||||||
|
const content = chatService['decodeMessageContent'](rawMessageContent, rawCompressContent)
|
||||||
|
|
||||||
|
if (localType === 1) {
|
||||||
|
msg.quotedContent = chatService['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 = '[链接]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseQuoteMessage(content: string): { content?: string; sender?: string; type?: string; svrid?: string } {
|
||||||
try {
|
try {
|
||||||
const normalized = this.normalizeAppMessageContent(content || '')
|
const normalized = this.normalizeAppMessageContent(content || '')
|
||||||
const referMsgStart = normalized.indexOf('<refermsg>')
|
const referMsgStart = normalized.indexOf('<refermsg>')
|
||||||
@@ -3553,6 +3595,7 @@ class ExportService {
|
|||||||
|
|
||||||
const referContent = this.extractXmlValue(referMsgXml, 'content')
|
const referContent = this.extractXmlValue(referMsgXml, 'content')
|
||||||
const referType = this.extractXmlValue(referMsgXml, 'type')
|
const referType = this.extractXmlValue(referMsgXml, 'type')
|
||||||
|
const svrid = this.extractXmlValue(referMsgXml, 'svrid')
|
||||||
let displayContent = referContent
|
let displayContent = referContent
|
||||||
|
|
||||||
switch (referType) {
|
switch (referType) {
|
||||||
@@ -3775,6 +3818,7 @@ class ExportService {
|
|||||||
if (quoteInfo.content) meta.quotedContent = quoteInfo.content
|
if (quoteInfo.content) meta.quotedContent = quoteInfo.content
|
||||||
if (quoteInfo.sender) meta.quotedSender = quoteInfo.sender
|
if (quoteInfo.sender) meta.quotedSender = quoteInfo.sender
|
||||||
if (quoteInfo.type) meta.quotedType = quoteInfo.type
|
if (quoteInfo.type) meta.quotedType = quoteInfo.type
|
||||||
|
if (quoteInfo.svrid) meta.quotedSvrid = quoteInfo.svrid
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appMsgKind === 'link') {
|
if (appMsgKind === 'link') {
|
||||||
@@ -6935,6 +6979,9 @@ class ExportService {
|
|||||||
|
|
||||||
await this.hydrateEmojiCaptionsForMessages(sessionId, collected.rows, control)
|
await this.hydrateEmojiCaptionsForMessages(sessionId, collected.rows, control)
|
||||||
|
|
||||||
|
// 解析引用消息
|
||||||
|
await this.resolveQuotedMessagesForExport(collected.rows, sessionId)
|
||||||
|
|
||||||
const voiceMessages = options.exportVoiceAsText
|
const voiceMessages = options.exportVoiceAsText
|
||||||
? collected.rows.filter(msg => msg.localType === 34)
|
? collected.rows.filter(msg => msg.localType === 34)
|
||||||
: []
|
: []
|
||||||
@@ -7139,7 +7186,8 @@ class ExportService {
|
|||||||
rawMyWxid,
|
rawMyWxid,
|
||||||
myDisplayName: myInfo.displayName || cleanedMyWxid
|
myDisplayName: myInfo.displayName || cleanedMyWxid
|
||||||
})
|
})
|
||||||
if (quotedReplyDisplay) {
|
// 对于媒体消息,不要让引用信息覆盖媒体路径
|
||||||
|
if (quotedReplyDisplay && !mediaItem) {
|
||||||
content = this.buildQuotedReplyText(quotedReplyDisplay)
|
content = this.buildQuotedReplyText(quotedReplyDisplay)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7674,6 +7722,9 @@ class ExportService {
|
|||||||
|
|
||||||
await this.hydrateEmojiCaptionsForMessages(sessionId, collected.rows, control)
|
await this.hydrateEmojiCaptionsForMessages(sessionId, collected.rows, control)
|
||||||
|
|
||||||
|
// 解析引用消息
|
||||||
|
await this.resolveQuotedMessagesForExport(collected.rows, sessionId)
|
||||||
|
|
||||||
const voiceMessages = options.exportVoiceAsText
|
const voiceMessages = options.exportVoiceAsText
|
||||||
? collected.rows.filter(msg => msg.localType === 34)
|
? collected.rows.filter(msg => msg.localType === 34)
|
||||||
: []
|
: []
|
||||||
@@ -8552,6 +8603,9 @@ class ExportService {
|
|||||||
|
|
||||||
await this.hydrateEmojiCaptionsForMessages(sessionId, collected.rows, control)
|
await this.hydrateEmojiCaptionsForMessages(sessionId, collected.rows, control)
|
||||||
|
|
||||||
|
// 解析引用消息
|
||||||
|
await this.resolveQuotedMessagesForExport(collected.rows, sessionId)
|
||||||
|
|
||||||
const voiceMessages = options.exportVoiceAsText
|
const voiceMessages = options.exportVoiceAsText
|
||||||
? collected.rows.filter(msg => msg.localType === 34)
|
? collected.rows.filter(msg => msg.localType === 34)
|
||||||
: []
|
: []
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export class WcdbCore {
|
|||||||
private wcdbMarkAllSessionsRead: any = null
|
private wcdbMarkAllSessionsRead: any = null
|
||||||
private wcdbGetMessages: any = null
|
private wcdbGetMessages: any = null
|
||||||
private wcdbGetMessageCount: any = null
|
private wcdbGetMessageCount: any = null
|
||||||
|
private wcdbGetMessageByServerId: any = null
|
||||||
private wcdbGetDisplayNames: any = null
|
private wcdbGetDisplayNames: any = null
|
||||||
private wcdbGetAvatarUrls: any = null
|
private wcdbGetAvatarUrls: any = null
|
||||||
private wcdbGetGroupMemberCount: any = null
|
private wcdbGetGroupMemberCount: any = null
|
||||||
@@ -824,6 +825,9 @@ export class WcdbCore {
|
|||||||
// wcdb_status wcdb_get_message_count(wcdb_handle handle, const char* username, int32_t* out_count)
|
// wcdb_status wcdb_get_message_count(wcdb_handle handle, const char* username, int32_t* out_count)
|
||||||
this.wcdbGetMessageCount = this.lib.func('int32 wcdb_get_message_count(int64 handle, const char* username, _Out_ int32* outCount)')
|
this.wcdbGetMessageCount = this.lib.func('int32 wcdb_get_message_count(int64 handle, const char* username, _Out_ int32* outCount)')
|
||||||
|
|
||||||
|
// wcdb_status wcdb_get_message_by_svrid(wcdb_handle handle, const char* session_id, const char* svrid, char** out_json)
|
||||||
|
this.wcdbGetMessageByServerId = this.lib.func('int32 wcdb_get_message_by_svrid(int64 handle, const char* sessionId, const char* svrid, _Out_ void** outJson)')
|
||||||
|
|
||||||
// wcdb_status wcdb_get_display_names(wcdb_handle handle, const char* usernames_json, char** out_json)
|
// wcdb_status wcdb_get_display_names(wcdb_handle handle, const char* usernames_json, char** out_json)
|
||||||
this.wcdbGetDisplayNames = this.lib.func('int32 wcdb_get_display_names(int64 handle, const char* usernamesJson, _Out_ void** outJson)')
|
this.wcdbGetDisplayNames = this.lib.func('int32 wcdb_get_display_names(int64 handle, const char* usernamesJson, _Out_ void** outJson)')
|
||||||
|
|
||||||
@@ -1807,6 +1811,30 @@ export class WcdbCore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getMessageByServerId(sessionId: string, svrid: string): Promise<{ success: boolean; row?: any; error?: string }> {
|
||||||
|
if (!this.ensureReady()) {
|
||||||
|
return { success: false, error: 'WCDB 未连接' }
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const outPtr = [null as any]
|
||||||
|
const result = this.wcdbGetMessageByServerId(this.handle, sessionId, svrid, outPtr)
|
||||||
|
if (result !== 0) {
|
||||||
|
return { success: false, error: `查询消息失败: ${result}` }
|
||||||
|
}
|
||||||
|
const jsonStr = this.decodeJsonPtr(outPtr[0])
|
||||||
|
if (!jsonStr) {
|
||||||
|
return { success: true, row: null }
|
||||||
|
}
|
||||||
|
const parsed = JSON.parse(jsonStr)
|
||||||
|
if (!parsed || Object.keys(parsed).length === 0) {
|
||||||
|
return { success: true, row: null }
|
||||||
|
}
|
||||||
|
return { success: true, row: parsed }
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, error: String(e) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getMessageCounts(sessionIds: string[]): Promise<{ success: boolean; counts?: Record<string, number>; error?: string }> {
|
async getMessageCounts(sessionIds: string[]): Promise<{ success: boolean; counts?: Record<string, number>; error?: string }> {
|
||||||
if (!this.ensureReady()) {
|
if (!this.ensureReady()) {
|
||||||
return { success: false, error: 'WCDB 未连接' }
|
return { success: false, error: 'WCDB 未连接' }
|
||||||
|
|||||||
@@ -229,6 +229,13 @@ export class WcdbService {
|
|||||||
return this.callWorker('getMessageCount', { sessionId })
|
return this.callWorker('getMessageCount', { sessionId })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据 server_id 查询单条消息
|
||||||
|
*/
|
||||||
|
async getMessageByServerId(sessionId: string, svrid: string): Promise<{ success: boolean; row?: any; error?: string }> {
|
||||||
|
return this.callWorker('getMessageByServerId', { sessionId, svrid })
|
||||||
|
}
|
||||||
|
|
||||||
async getMessageCounts(sessionIds: string[]): Promise<{ success: boolean; counts?: Record<string, number>; error?: string }> {
|
async getMessageCounts(sessionIds: string[]): Promise<{ success: boolean; counts?: Record<string, number>; error?: string }> {
|
||||||
return this.callWorker('getMessageCounts', { sessionIds })
|
return this.callWorker('getMessageCounts', { sessionIds })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,9 @@ if (parentPort) {
|
|||||||
case 'getMessageCount':
|
case 'getMessageCount':
|
||||||
result = await core.getMessageCount(payload.sessionId)
|
result = await core.getMessageCount(payload.sessionId)
|
||||||
break
|
break
|
||||||
|
case 'getMessageByServerId':
|
||||||
|
result = await core.getMessageByServerId(payload.sessionId, payload.svrid)
|
||||||
|
break
|
||||||
case 'getMessageCounts':
|
case 'getMessageCounts':
|
||||||
result = await core.getMessageCounts(payload.sessionIds)
|
result = await core.getMessageCounts(payload.sessionIds)
|
||||||
break
|
break
|
||||||
|
|||||||
1108
package-lock.json
generated
1108
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,7 @@
|
|||||||
"electron:build": "npm run build"
|
"electron:build": "npm run build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@vscode/sudo-prompt": "^9.3.2",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
"echarts-for-react": "^3.0.2",
|
"echarts-for-react": "^3.0.2",
|
||||||
"electron-store": "^11.0.2",
|
"electron-store": "^11.0.2",
|
||||||
@@ -43,7 +44,6 @@
|
|||||||
"remark-gfm": "^4.0.1",
|
"remark-gfm": "^4.0.1",
|
||||||
"sherpa-onnx-node": "^1.10.38",
|
"sherpa-onnx-node": "^1.10.38",
|
||||||
"silk-wasm": "^3.7.1",
|
"silk-wasm": "^3.7.1",
|
||||||
"@vscode/sudo-prompt": "^9.3.2",
|
|
||||||
"wechat-emojis": "^1.0.2",
|
"wechat-emojis": "^1.0.2",
|
||||||
"zustand": "^5.0.2"
|
"zustand": "^5.0.2"
|
||||||
},
|
},
|
||||||
@@ -51,12 +51,13 @@
|
|||||||
"@electron/rebuild": "^4.0.2",
|
"@electron/rebuild": "^4.0.2",
|
||||||
"@types/react": "^19.1.0",
|
"@types/react": "^19.1.0",
|
||||||
"@types/react-dom": "^19.1.0",
|
"@types/react-dom": "^19.1.0",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^6.0.1",
|
||||||
"electron": "^41.1.1",
|
"electron": "^41.1.1",
|
||||||
"electron-builder": "^26.8.1",
|
"electron-builder": "^26.8.1",
|
||||||
|
"esbuild": "^0.28.0",
|
||||||
"sass": "^1.98.0",
|
"sass": "^1.98.0",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"typescript": "^6.0.2",
|
"typescript": "^6.0.3",
|
||||||
"vite": "^8.0.10",
|
"vite": "^8.0.10",
|
||||||
"vite-plugin-electron": "^0.28.8",
|
"vite-plugin-electron": "^0.28.8",
|
||||||
"vite-plugin-electron-renderer": "^0.14.6"
|
"vite-plugin-electron-renderer": "^0.14.6"
|
||||||
|
|||||||
Binary file not shown.
@@ -9687,7 +9687,7 @@ function MessageBubble({
|
|||||||
// 渲染消息内容
|
// 渲染消息内容
|
||||||
const renderContent = () => {
|
const renderContent = () => {
|
||||||
if (isImage) {
|
if (isImage) {
|
||||||
return (
|
const imageContent = (
|
||||||
<div
|
<div
|
||||||
ref={imageContainerRef}
|
ref={imageContainerRef}
|
||||||
className={`image-stage ${imageStageLockHeight ? 'locked' : ''}`}
|
className={`image-stage ${imageStageLockHeight ? 'locked' : ''}`}
|
||||||
@@ -9745,13 +9745,24 @@ function MessageBubble({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (hasQuote) {
|
||||||
|
return renderBubbleWithQuote(
|
||||||
|
renderQuotedMessageBlock(renderTextWithEmoji(cleanMessageContent(quotedContent))),
|
||||||
|
imageContent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="bubble-content">{imageContent}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
// 视频消息
|
// 视频消息
|
||||||
if (isVideo) {
|
if (isVideo) {
|
||||||
|
let videoContent: React.ReactNode
|
||||||
|
|
||||||
// 未进入可视区域时显示占位符
|
// 未进入可视区域时显示占位符
|
||||||
if (!isVideoVisible) {
|
if (!isVideoVisible) {
|
||||||
return (
|
videoContent = (
|
||||||
<div className="video-placeholder" ref={videoContainerRef as React.RefObject<HTMLDivElement>}>
|
<div className="video-placeholder" ref={videoContainerRef as React.RefObject<HTMLDivElement>}>
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
||||||
<polygon points="23 7 16 12 23 17 23 7"></polygon>
|
<polygon points="23 7 16 12 23 17 23 7"></polygon>
|
||||||
@@ -9759,20 +9770,16 @@ function MessageBubble({
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
} else if (videoLoading) {
|
||||||
|
|
||||||
// 加载中
|
// 加载中
|
||||||
if (videoLoading) {
|
videoContent = (
|
||||||
return (
|
|
||||||
<div className="video-loading" ref={videoContainerRef as React.RefObject<HTMLDivElement>}>
|
<div className="video-loading" ref={videoContainerRef as React.RefObject<HTMLDivElement>}>
|
||||||
<Loader2 size={20} className="spin" />
|
<Loader2 size={20} className="spin" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
} else if (!videoInfo?.exists || !videoInfo.videoUrl) {
|
||||||
|
|
||||||
// 视频不存在 - 添加点击重试功能
|
// 视频不存在 - 添加点击重试功能
|
||||||
if (!videoInfo?.exists || !videoInfo.videoUrl) {
|
videoContent = (
|
||||||
return (
|
|
||||||
<button
|
<button
|
||||||
className={`video-unavailable ${videoClicked ? 'clicked' : ''}`}
|
className={`video-unavailable ${videoClicked ? 'clicked' : ''}`}
|
||||||
ref={videoContainerRef as React.RefObject<HTMLButtonElement>}
|
ref={videoContainerRef as React.RefObject<HTMLButtonElement>}
|
||||||
@@ -9792,11 +9799,10 @@ function MessageBubble({
|
|||||||
<span className="video-action">{videoClicked ? '已点击…' : '点击重试'}</span>
|
<span className="video-action">{videoClicked ? '已点击…' : '点击重试'}</span>
|
||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
} else {
|
||||||
|
|
||||||
// 默认显示缩略图,点击打开独立播放窗口
|
// 默认显示缩略图,点击打开独立播放窗口
|
||||||
const thumbSrc = videoInfo.thumbUrl || videoInfo.coverUrl
|
const thumbSrc = videoInfo.thumbUrl || videoInfo.coverUrl
|
||||||
return (
|
videoContent = (
|
||||||
<div className="video-thumb-wrapper" ref={videoContainerRef as React.RefObject<HTMLDivElement>} onClick={handlePlayVideo}>
|
<div className="video-thumb-wrapper" ref={videoContainerRef as React.RefObject<HTMLDivElement>} onClick={handlePlayVideo}>
|
||||||
{thumbSrc ? (
|
{thumbSrc ? (
|
||||||
<img src={thumbSrc} alt="视频缩略图" className="video-thumb" loading="lazy" decoding="async" />
|
<img src={thumbSrc} alt="视频缩略图" className="video-thumb" loading="lazy" decoding="async" />
|
||||||
@@ -9815,6 +9821,16 @@ function MessageBubble({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasQuote) {
|
||||||
|
return renderBubbleWithQuote(
|
||||||
|
renderQuotedMessageBlock(renderTextWithEmoji(cleanMessageContent(quotedContent))),
|
||||||
|
videoContent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="bubble-content">{videoContent}</div>
|
||||||
|
}
|
||||||
|
|
||||||
if (isVoice) {
|
if (isVoice) {
|
||||||
const durationText = message.voiceDurationSeconds ? `${message.voiceDurationSeconds}"` : ''
|
const durationText = message.voiceDurationSeconds ? `${message.voiceDurationSeconds}"` : ''
|
||||||
const handleToggle = async () => {
|
const handleToggle = async () => {
|
||||||
@@ -9900,7 +9916,7 @@ function MessageBubble({
|
|||||||
void requestVoiceTranscript()
|
void requestVoiceTranscript()
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
const voiceContent = (
|
||||||
<div className="voice-stack">
|
<div className="voice-stack">
|
||||||
<div className={`voice-message ${isVoicePlaying ? 'playing' : ''}`} onClick={handleToggle}>
|
<div className={`voice-message ${isVoicePlaying ? 'playing' : ''}`} onClick={handleToggle}>
|
||||||
<button
|
<button
|
||||||
@@ -9983,6 +9999,15 @@ function MessageBubble({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (hasQuote) {
|
||||||
|
return renderBubbleWithQuote(
|
||||||
|
renderQuotedMessageBlock(renderTextWithEmoji(cleanMessageContent(quotedContent))),
|
||||||
|
voiceContent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className="bubble-content">{voiceContent}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
// 名片消息
|
// 名片消息
|
||||||
|
|||||||
Reference in New Issue
Block a user