mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
This commit is contained in:
@@ -2974,7 +2974,9 @@ class ChatService {
|
|||||||
const localType = this.getRowInt(row, ['local_type', 'localType', 'type', 'msg_type', 'msgType', 'WCDB_CT_local_type'], 1)
|
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'])
|
const isSendRaw = this.getRowField(row, ['computed_is_send', 'computedIsSend', 'is_send', 'isSend', 'WCDB_CT_is_send'])
|
||||||
let isSend = isSendRaw === null ? null : parseInt(isSendRaw, 10)
|
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)
|
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)) {
|
if (senderUsername && (myWxidLower || cleanedWxidLower)) {
|
||||||
@@ -4385,7 +4387,18 @@ class ChatService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private stripSenderPrefix(content: string): string {
|
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|<br\s*\/?>)\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|<br\s*\/?>)/i.exec(normalized)
|
||||||
|
if (!match?.[1]) return null
|
||||||
|
|
||||||
|
const candidate = match[1].trim()
|
||||||
|
return candidate || null
|
||||||
}
|
}
|
||||||
|
|
||||||
private decodeHtmlEntities(content: string): string {
|
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),
|
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)),
|
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),
|
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,
|
rawContent: rawContent,
|
||||||
content: rawContent, // 添加原始内容供视频MD5解析使用
|
content: rawContent, // 添加原始内容供视频MD5解析使用
|
||||||
parsedContent: this.parseMessageContent(rawContent, this.getRowInt(row, ['local_type', 'localType', 'type', 'msg_type', 'msgType', 'WCDB_CT_local_type'], 0))
|
parsedContent: this.parseMessageContent(rawContent, this.getRowInt(row, ['local_type', 'localType', 'type', 'msg_type', 'msgType', 'WCDB_CT_local_type'], 0))
|
||||||
|
|||||||
@@ -340,6 +340,7 @@ class HttpService {
|
|||||||
const trimmedRows = allRows.slice(0, limit)
|
const trimmedRows = allRows.slice(0, limit)
|
||||||
const finalHasMore = hasMore || allRows.length > limit
|
const finalHasMore = hasMore || allRows.length > limit
|
||||||
const messages = chatService.mapRowsToMessagesForApi(trimmedRows)
|
const messages = chatService.mapRowsToMessagesForApi(trimmedRows)
|
||||||
|
await this.backfillMissingSenderUsernames(talker, messages)
|
||||||
return { success: true, messages, hasMore: finalHasMore }
|
return { success: true, messages, hasMore: finalHasMore }
|
||||||
} finally {
|
} finally {
|
||||||
await wcdbService.closeMessageCursor(cursor)
|
await wcdbService.closeMessageCursor(cursor)
|
||||||
@@ -359,6 +360,41 @@ class HttpService {
|
|||||||
return Math.min(Math.max(parsed, min), max)
|
return Math.min(Math.max(parsed, min), max)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async backfillMissingSenderUsernames(talker: string, messages: Message[]): Promise<void> {
|
||||||
|
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 {
|
private parseBooleanParam(url: URL, keys: string[], defaultValue: boolean = false): boolean {
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const raw = url.searchParams.get(key)
|
const raw = url.searchParams.get(key)
|
||||||
@@ -778,6 +814,49 @@ class HttpService {
|
|||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lookupGroupNickname(groupNicknamesMap: Map<string, string>, 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<string, string>,
|
||||||
|
groupNicknamesMap: Map<string, string>
|
||||||
|
): { 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 格式
|
* 转换为 ChatLab 格式
|
||||||
*/
|
*/
|
||||||
@@ -817,36 +896,24 @@ class HttpService {
|
|||||||
// 构建成员列表
|
// 构建成员列表
|
||||||
const memberMap = new Map<string, ChatLabMember>()
|
const memberMap = new Map<string, ChatLabMember>()
|
||||||
for (const msg of messages) {
|
for (const msg of messages) {
|
||||||
const sender = msg.senderUsername || ''
|
const senderInfo = this.resolveChatLabSenderInfo(msg, talkerId, talkerName, myWxid, isGroup, senderNames, groupNicknamesMap)
|
||||||
if (sender && !memberMap.has(sender)) {
|
if (!memberMap.has(senderInfo.sender)) {
|
||||||
const displayName = senderNames[sender] || sender
|
memberMap.set(senderInfo.sender, {
|
||||||
const isSelf = sender === myWxid || sender.toLowerCase() === myWxid.toLowerCase()
|
platformId: senderInfo.sender,
|
||||||
// 获取群昵称(尝试多种方式)
|
accountName: senderInfo.accountName,
|
||||||
const groupNickname = isGroup
|
groupNickname: senderInfo.groupNickname
|
||||||
? (groupNicknamesMap.get(sender) || groupNicknamesMap.get(sender.toLowerCase()) || '')
|
|
||||||
: ''
|
|
||||||
memberMap.set(sender, {
|
|
||||||
platformId: sender,
|
|
||||||
accountName: isSelf ? '我' : displayName,
|
|
||||||
groupNickname: groupNickname || undefined
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换消息
|
// 转换消息
|
||||||
const chatLabMessages: ChatLabMessage[] = messages.map(msg => {
|
const chatLabMessages: ChatLabMessage[] = messages.map(msg => {
|
||||||
const sender = msg.senderUsername || ''
|
const senderInfo = this.resolveChatLabSenderInfo(msg, talkerId, talkerName, myWxid, isGroup, senderNames, groupNicknamesMap)
|
||||||
const isSelf = msg.isSend === 1 || sender === myWxid
|
|
||||||
const accountName = isSelf ? '我' : (senderNames[sender] || sender)
|
|
||||||
// 获取该发送者的群昵称
|
|
||||||
const groupNickname = isGroup
|
|
||||||
? (groupNicknamesMap.get(sender) || groupNicknamesMap.get(sender.toLowerCase()) || '')
|
|
||||||
: ''
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sender,
|
sender: senderInfo.sender,
|
||||||
accountName,
|
accountName: senderInfo.accountName,
|
||||||
groupNickname: groupNickname || undefined,
|
groupNickname: senderInfo.groupNickname,
|
||||||
timestamp: msg.createTime,
|
timestamp: msg.createTime,
|
||||||
type: this.mapMessageType(msg.localType, msg),
|
type: this.mapMessageType(msg.localType, msg),
|
||||||
content: this.getMessageContent(msg),
|
content: this.getMessageContent(msg),
|
||||||
|
|||||||
Reference in New Issue
Block a user