diff --git a/electron/services/chatService.ts b/electron/services/chatService.ts
index 470933a..6f104fd 100644
--- a/electron/services/chatService.ts
+++ b/electron/services/chatService.ts
@@ -2974,7 +2974,9 @@ class ChatService {
const localType = this.getRowInt(row, ['local_type', 'localType', 'type', 'msg_type', 'msgType', 'WCDB_CT_local_type'], 1)
const isSendRaw = this.getRowField(row, ['computed_is_send', 'computedIsSend', 'is_send', 'isSend', 'WCDB_CT_is_send'])
let isSend = isSendRaw === null ? null : parseInt(isSendRaw, 10)
- const senderUsername = this.getRowField(row, ['sender_username', 'senderUsername', 'sender', 'WCDB_CT_sender_username']) || null
+ const senderUsername = this.getRowField(row, ['sender_username', 'senderUsername', 'sender', 'WCDB_CT_sender_username'])
+ || this.extractSenderUsernameFromContent(content)
+ || null
const createTime = this.getRowInt(row, ['create_time', 'createTime', 'createtime', 'msg_create_time', 'msgCreateTime', 'msg_time', 'msgTime', 'time', 'WCDB_CT_create_time'], 0)
if (senderUsername && (myWxidLower || cleanedWxidLower)) {
@@ -4385,7 +4387,18 @@ class ChatService {
}
private stripSenderPrefix(content: string): string {
- return content.replace(/^[\s]*([a-zA-Z0-9_-]+):(?!\/\/)\s*/, '')
+ return content.replace(/^[\s]*([a-zA-Z0-9_@-]+):(?!\/\/)(?:\s*(?:\r?\n|
)\s*|\s*)/i, '')
+ }
+
+ private extractSenderUsernameFromContent(content: string): string | null {
+ if (!content) return null
+
+ const normalized = this.cleanUtf16(this.decodeHtmlEntities(String(content)))
+ const match = /^\s*([a-zA-Z0-9_@-]{4,}):(?!\/\/)\s*(?:\r?\n|
)/i.exec(normalized)
+ if (!match?.[1]) return null
+
+ const candidate = match[1].trim()
+ return candidate || null
}
private decodeHtmlEntities(content: string): string {
@@ -6594,7 +6607,9 @@ class ChatService {
createTime: this.getRowInt(row, ['create_time', 'createTime', 'createtime', 'msg_create_time', 'msgCreateTime', 'msg_time', 'msgTime', 'time', 'WCDB_CT_create_time'], 0),
sortSeq: this.getRowInt(row, ['sort_seq', 'sortSeq', 'seq', 'sequence', 'WCDB_CT_sort_seq'], this.getRowInt(row, ['create_time', 'createTime', 'createtime', 'msg_create_time', 'msgCreateTime', 'msg_time', 'msgTime', 'time', 'WCDB_CT_create_time'], 0)),
isSend: this.getRowInt(row, ['computed_is_send', 'computedIsSend', 'is_send', 'isSend', 'WCDB_CT_is_send'], 0),
- senderUsername: this.getRowField(row, ['sender_username', 'senderUsername', 'sender', 'WCDB_CT_sender_username']) || null,
+ senderUsername: this.getRowField(row, ['sender_username', 'senderUsername', 'sender', 'WCDB_CT_sender_username'])
+ || this.extractSenderUsernameFromContent(rawContent)
+ || null,
rawContent: rawContent,
content: rawContent, // 添加原始内容供视频MD5解析使用
parsedContent: this.parseMessageContent(rawContent, this.getRowInt(row, ['local_type', 'localType', 'type', 'msg_type', 'msgType', 'WCDB_CT_local_type'], 0))
diff --git a/electron/services/httpService.ts b/electron/services/httpService.ts
index 86ea6c9..bfb8ce1 100644
--- a/electron/services/httpService.ts
+++ b/electron/services/httpService.ts
@@ -340,6 +340,7 @@ class HttpService {
const trimmedRows = allRows.slice(0, limit)
const finalHasMore = hasMore || allRows.length > limit
const messages = chatService.mapRowsToMessagesForApi(trimmedRows)
+ await this.backfillMissingSenderUsernames(talker, messages)
return { success: true, messages, hasMore: finalHasMore }
} finally {
await wcdbService.closeMessageCursor(cursor)
@@ -359,6 +360,41 @@ class HttpService {
return Math.min(Math.max(parsed, min), max)
}
+ private async backfillMissingSenderUsernames(talker: string, messages: Message[]): Promise {
+ if (!talker.endsWith('@chatroom')) return
+
+ const targets = messages.filter((msg) => !String(msg.senderUsername || '').trim())
+ if (targets.length === 0) return
+
+ const myWxid = (this.configService.get('myWxid') || '').trim()
+ for (const msg of targets) {
+ const localId = Number(msg.localId || 0)
+ if (Number.isFinite(localId) && localId > 0) {
+ try {
+ const detail = await wcdbService.getMessageById(talker, localId)
+ if (detail.success && detail.message) {
+ const hydrated = chatService.mapRowsToMessagesForApi([detail.message])[0]
+ if (hydrated?.senderUsername) {
+ msg.senderUsername = hydrated.senderUsername
+ }
+ if ((msg.isSend === null || msg.isSend === undefined) && hydrated?.isSend !== undefined) {
+ msg.isSend = hydrated.isSend
+ }
+ if (!msg.rawContent && hydrated?.rawContent) {
+ msg.rawContent = hydrated.rawContent
+ }
+ }
+ } catch (error) {
+ console.warn('[HttpService] backfill sender failed:', error)
+ }
+ }
+
+ if (!msg.senderUsername && msg.isSend === 1 && myWxid) {
+ msg.senderUsername = myWxid
+ }
+ }
+ }
+
private parseBooleanParam(url: URL, keys: string[], defaultValue: boolean = false): boolean {
for (const key of keys) {
const raw = url.searchParams.get(key)
@@ -778,6 +814,49 @@ class HttpService {
return {}
}
+ private lookupGroupNickname(groupNicknamesMap: Map, sender: string): string {
+ if (!sender) return ''
+ return groupNicknamesMap.get(sender) || groupNicknamesMap.get(sender.toLowerCase()) || ''
+ }
+
+ private resolveChatLabSenderInfo(
+ msg: Message,
+ talkerId: string,
+ talkerName: string,
+ myWxid: string,
+ isGroup: boolean,
+ senderNames: Record,
+ groupNicknamesMap: Map
+ ): { sender: string; accountName: string; groupNickname?: string } {
+ let sender = String(msg.senderUsername || '').trim()
+ let usedUnknownPlaceholder = false
+ const sameAsMe = sender && myWxid && sender.toLowerCase() === myWxid.toLowerCase()
+ const isSelf = msg.isSend === 1 || sameAsMe
+
+ if (!sender && isSelf && myWxid) {
+ sender = myWxid
+ }
+
+ if (!sender) {
+ if (msg.localType === 10000 || msg.localType === 266287972401) {
+ sender = talkerId
+ } else {
+ sender = `unknown_sender_${msg.localId || msg.createTime || 0}`
+ usedUnknownPlaceholder = true
+ }
+ }
+
+ const groupNickname = isGroup ? this.lookupGroupNickname(groupNicknamesMap, sender) : ''
+ const displayName = senderNames[sender] || groupNickname || (usedUnknownPlaceholder ? '' : sender)
+ const accountName = isSelf ? '我' : (displayName || '未知发送者')
+
+ return {
+ sender,
+ accountName,
+ groupNickname: groupNickname || undefined
+ }
+ }
+
/**
* 转换为 ChatLab 格式
*/
@@ -817,36 +896,24 @@ class HttpService {
// 构建成员列表
const memberMap = new Map()
for (const msg of messages) {
- const sender = msg.senderUsername || ''
- if (sender && !memberMap.has(sender)) {
- const displayName = senderNames[sender] || sender
- const isSelf = sender === myWxid || sender.toLowerCase() === myWxid.toLowerCase()
- // 获取群昵称(尝试多种方式)
- const groupNickname = isGroup
- ? (groupNicknamesMap.get(sender) || groupNicknamesMap.get(sender.toLowerCase()) || '')
- : ''
- memberMap.set(sender, {
- platformId: sender,
- accountName: isSelf ? '我' : displayName,
- groupNickname: groupNickname || undefined
+ const senderInfo = this.resolveChatLabSenderInfo(msg, talkerId, talkerName, myWxid, isGroup, senderNames, groupNicknamesMap)
+ if (!memberMap.has(senderInfo.sender)) {
+ memberMap.set(senderInfo.sender, {
+ platformId: senderInfo.sender,
+ accountName: senderInfo.accountName,
+ groupNickname: senderInfo.groupNickname
})
}
}
// 转换消息
const chatLabMessages: ChatLabMessage[] = messages.map(msg => {
- const sender = msg.senderUsername || ''
- const isSelf = msg.isSend === 1 || sender === myWxid
- const accountName = isSelf ? '我' : (senderNames[sender] || sender)
- // 获取该发送者的群昵称
- const groupNickname = isGroup
- ? (groupNicknamesMap.get(sender) || groupNicknamesMap.get(sender.toLowerCase()) || '')
- : ''
+ const senderInfo = this.resolveChatLabSenderInfo(msg, talkerId, talkerName, myWxid, isGroup, senderNames, groupNicknamesMap)
return {
- sender,
- accountName,
- groupNickname: groupNickname || undefined,
+ sender: senderInfo.sender,
+ accountName: senderInfo.accountName,
+ groupNickname: senderInfo.groupNickname,
timestamp: msg.createTime,
type: this.mapMessageType(msg.localType, msg),
content: this.getMessageContent(msg),