mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
补齐别的格式
This commit is contained in:
@@ -1634,7 +1634,13 @@ class ExportService {
|
||||
if (type === '6') return title ? `[文件] ${title}` : '[文件]'
|
||||
if (type === '19') return this.formatForwardChatRecordContent(normalizedContent)
|
||||
if (type === '33' || type === '36') return title ? `[小程序] ${title}` : '[小程序]'
|
||||
if (type === '57') return title || '[引用消息]'
|
||||
if (type === '57') {
|
||||
const quoteDisplay = this.extractQuotedReplyDisplay(content)
|
||||
if (quoteDisplay) {
|
||||
return this.buildQuotedReplyText(quoteDisplay)
|
||||
}
|
||||
return title || '[引用消息]'
|
||||
}
|
||||
if (type === '5' || type === '49') return title ? `[链接] ${title}` : '[链接]'
|
||||
return title ? `[链接] ${title}` : '[链接]'
|
||||
}
|
||||
@@ -1643,6 +1649,10 @@ class ExportService {
|
||||
case 266287972401: return this.cleanSystemMessage(content) // 拍一拍
|
||||
case 244813135921: {
|
||||
// 引用消息
|
||||
const quoteDisplay = this.extractQuotedReplyDisplay(content)
|
||||
if (quoteDisplay) {
|
||||
return this.buildQuotedReplyText(quoteDisplay)
|
||||
}
|
||||
const title = this.extractXmlValue(content, 'title')
|
||||
return title || '[引用消息]'
|
||||
}
|
||||
@@ -1676,7 +1686,13 @@ class ExportService {
|
||||
if (xmlType === '6') return title ? `[文件] ${title}` : '[文件]'
|
||||
if (xmlType === '19') return this.formatForwardChatRecordContent(normalizedContent)
|
||||
if (xmlType === '33' || xmlType === '36') return title ? `[小程序] ${title}` : '[小程序]'
|
||||
if (xmlType === '57') return title || '[引用消息]'
|
||||
if (xmlType === '57') {
|
||||
const quoteDisplay = this.extractQuotedReplyDisplay(content)
|
||||
if (quoteDisplay) {
|
||||
return this.buildQuotedReplyText(quoteDisplay)
|
||||
}
|
||||
return title || '[引用消息]'
|
||||
}
|
||||
if (xmlType === '5' || xmlType === '49') return title ? `[链接] ${title}` : '[链接]'
|
||||
|
||||
// 有 title 就返回 title
|
||||
@@ -1801,6 +1817,10 @@ class ExportService {
|
||||
return `[小程序]${appName}`
|
||||
}
|
||||
if (subType === 57) {
|
||||
const quoteDisplay = this.extractQuotedReplyDisplay(safeContent)
|
||||
if (quoteDisplay) {
|
||||
return this.buildQuotedReplyText(quoteDisplay)
|
||||
}
|
||||
return title || '[引用消息]'
|
||||
}
|
||||
if (title) {
|
||||
@@ -1812,6 +1832,151 @@ class ExportService {
|
||||
return '[其他消息]'
|
||||
}
|
||||
|
||||
private formatQuotedReferencePreview(content: string, type?: string): string {
|
||||
const safeContent = content || ''
|
||||
const referType = Number.parseInt(String(type || ''), 10)
|
||||
if (!Number.isFinite(referType)) {
|
||||
const sanitized = this.sanitizeQuotedContent(safeContent)
|
||||
return sanitized || '[消息]'
|
||||
}
|
||||
|
||||
if (referType === 49) {
|
||||
const normalized = this.normalizeAppMessageContent(safeContent)
|
||||
const title =
|
||||
this.extractXmlValue(normalized, 'title') ||
|
||||
this.extractXmlValue(normalized, 'filename') ||
|
||||
this.extractXmlValue(normalized, 'appname')
|
||||
if (title) return this.stripSenderPrefix(title)
|
||||
|
||||
const subTypeRaw = this.extractAppMessageType(normalized)
|
||||
const subType = subTypeRaw ? parseInt(subTypeRaw, 10) : 0
|
||||
if (subType === 6) return '[文件]'
|
||||
if (subType === 19) return '[聊天记录]'
|
||||
if (subType === 33 || subType === 36) return '[小程序]'
|
||||
return '[链接]'
|
||||
}
|
||||
|
||||
return this.formatPlainExportContent(safeContent, referType, { exportVoiceAsText: false }) || '[消息]'
|
||||
}
|
||||
|
||||
private resolveQuotedSenderUsername(fromusr?: string, chatusr?: string): string {
|
||||
const normalizedChatUsr = String(chatusr || '').trim()
|
||||
const normalizedFromUsr = String(fromusr || '').trim()
|
||||
|
||||
if (normalizedChatUsr) {
|
||||
return normalizedChatUsr
|
||||
}
|
||||
|
||||
if (normalizedFromUsr.endsWith('@chatroom')) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return normalizedFromUsr
|
||||
}
|
||||
|
||||
private buildQuotedReplyText(display: {
|
||||
replyText: string
|
||||
quotedSender?: string
|
||||
quotedPreview: string
|
||||
}): string {
|
||||
const quoteLabel = display.quotedSender
|
||||
? `${display.quotedSender}:${display.quotedPreview}`
|
||||
: display.quotedPreview
|
||||
if (display.replyText) {
|
||||
return `${display.replyText}[引用 ${quoteLabel}]`
|
||||
}
|
||||
return `[引用 ${quoteLabel}]`
|
||||
}
|
||||
|
||||
private extractQuotedReplyDisplay(content: string): {
|
||||
replyText: string
|
||||
quotedSender?: string
|
||||
quotedPreview: string
|
||||
} | null {
|
||||
try {
|
||||
const normalized = this.normalizeAppMessageContent(content || '')
|
||||
const referMsgStart = normalized.indexOf('<refermsg>')
|
||||
const referMsgEnd = normalized.indexOf('</refermsg>')
|
||||
if (referMsgStart === -1 || referMsgEnd === -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
const referMsgXml = normalized.substring(referMsgStart, referMsgEnd + 11)
|
||||
const quoteInfo = this.parseQuoteMessage(normalized)
|
||||
const replyText = this.stripSenderPrefix(this.extractXmlValue(normalized, 'title') || '')
|
||||
const quotedPreview = this.formatQuotedReferencePreview(
|
||||
this.extractXmlValue(referMsgXml, 'content'),
|
||||
this.extractXmlValue(referMsgXml, 'type')
|
||||
)
|
||||
|
||||
if (!replyText && !quotedPreview) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
replyText,
|
||||
quotedSender: quoteInfo.sender || undefined,
|
||||
quotedPreview: quotedPreview || '[消息]'
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private async resolveQuotedReplyDisplayWithNames(args: {
|
||||
content: string
|
||||
isGroup: boolean
|
||||
displayNamePreference: ExportOptions['displayNamePreference']
|
||||
getContact: (username: string) => Promise<{ success: boolean; contact?: any; error?: string }>
|
||||
groupNicknamesMap: Map<string, string>
|
||||
cleanedMyWxid: string
|
||||
rawMyWxid?: string
|
||||
myDisplayName?: string
|
||||
}): Promise<{
|
||||
replyText: string
|
||||
quotedSender?: string
|
||||
quotedPreview: string
|
||||
} | null> {
|
||||
const base = this.extractQuotedReplyDisplay(args.content)
|
||||
if (!base) return null
|
||||
if (base.quotedSender) return base
|
||||
|
||||
const normalized = this.normalizeAppMessageContent(args.content || '')
|
||||
const referMsgStart = normalized.indexOf('<refermsg>')
|
||||
const referMsgEnd = normalized.indexOf('</refermsg>')
|
||||
if (referMsgStart === -1 || referMsgEnd === -1) {
|
||||
return base
|
||||
}
|
||||
|
||||
const referMsgXml = normalized.substring(referMsgStart, referMsgEnd + 11)
|
||||
const quotedSenderUsername = this.resolveQuotedSenderUsername(
|
||||
this.extractXmlValue(referMsgXml, 'fromusr'),
|
||||
this.extractXmlValue(referMsgXml, 'chatusr')
|
||||
)
|
||||
if (!quotedSenderUsername) {
|
||||
return base
|
||||
}
|
||||
|
||||
const isQuotedSelf = this.isSameWxid(quotedSenderUsername, args.cleanedMyWxid)
|
||||
const fallbackDisplayName = isQuotedSelf
|
||||
? (args.myDisplayName || quotedSenderUsername)
|
||||
: quotedSenderUsername
|
||||
|
||||
const profile = await this.resolveExportDisplayProfile(
|
||||
quotedSenderUsername,
|
||||
args.displayNamePreference,
|
||||
args.getContact,
|
||||
args.groupNicknamesMap,
|
||||
fallbackDisplayName,
|
||||
isQuotedSelf ? [args.rawMyWxid, args.cleanedMyWxid] : []
|
||||
)
|
||||
|
||||
return {
|
||||
...base,
|
||||
quotedSender: profile.displayName || fallbackDisplayName || base.quotedSender
|
||||
}
|
||||
}
|
||||
|
||||
private parseDurationSeconds(value: string): number | null {
|
||||
const numeric = Number(value)
|
||||
if (!Number.isFinite(numeric) || numeric <= 0) return null
|
||||
@@ -2493,6 +2658,15 @@ class ExportService {
|
||||
}
|
||||
}
|
||||
|
||||
private getExportPlatformMessageId(msg: { serverIdRaw?: unknown; serverId?: unknown }): string | undefined {
|
||||
const value = this.normalizeUnsignedIntToken(msg.serverIdRaw ?? msg.serverId)
|
||||
return value !== '0' ? value : undefined
|
||||
}
|
||||
|
||||
private getExportReplyToMessageId(content: string): string | undefined {
|
||||
return this.extractChatLabReplyToMessageId(content)
|
||||
}
|
||||
|
||||
private extractArkmeAppMessageMeta(content: string, localType: number): Record<string, any> | null {
|
||||
if (!content) return null
|
||||
|
||||
@@ -4944,6 +5118,20 @@ class ExportService {
|
||||
)
|
||||
}
|
||||
|
||||
const quotedReplyDisplay = await this.resolveQuotedReplyDisplayWithNames({
|
||||
content: msg.content,
|
||||
isGroup,
|
||||
displayNamePreference: options.displayNamePreference,
|
||||
getContact: getContactCached,
|
||||
groupNicknamesMap,
|
||||
cleanedMyWxid,
|
||||
rawMyWxid,
|
||||
myDisplayName: myInfo.displayName || cleanedMyWxid
|
||||
})
|
||||
if (quotedReplyDisplay) {
|
||||
content = this.buildQuotedReplyText(quotedReplyDisplay)
|
||||
}
|
||||
|
||||
// 获取发送者信息用于名称显示
|
||||
const senderWxid = msg.senderUsername
|
||||
const contact = senderWxid
|
||||
@@ -4987,6 +5175,12 @@ class ExportService {
|
||||
senderAvatarKey: msg.senderUsername
|
||||
}
|
||||
|
||||
const platformMessageId = this.getExportPlatformMessageId(msg)
|
||||
if (platformMessageId) msgObj.platformMessageId = platformMessageId
|
||||
|
||||
const replyToMessageId = this.getExportReplyToMessageId(msg.content)
|
||||
if (replyToMessageId) msgObj.replyToMessageId = replyToMessageId
|
||||
|
||||
const appMsgMeta = this.extractArkmeAppMessageMeta(msg.content, msg.localType)
|
||||
if (appMsgMeta) {
|
||||
if (
|
||||
@@ -4996,6 +5190,10 @@ class ExportService {
|
||||
Object.assign(msgObj, appMsgMeta)
|
||||
}
|
||||
}
|
||||
if (quotedReplyDisplay) {
|
||||
if (quotedReplyDisplay.quotedSender) msgObj.quotedSender = quotedReplyDisplay.quotedSender
|
||||
if (quotedReplyDisplay.quotedPreview) msgObj.quotedContent = quotedReplyDisplay.quotedPreview
|
||||
}
|
||||
|
||||
if (options.format === 'arkme-json') {
|
||||
const contactCardMeta = this.extractArkmeContactCardMeta(msg.content, msg.localType)
|
||||
@@ -5193,6 +5391,8 @@ class ExportService {
|
||||
senderID,
|
||||
source: message.source
|
||||
}
|
||||
if (message.platformMessageId) compactMessage.platformMessageId = message.platformMessageId
|
||||
if (message.replyToMessageId) compactMessage.replyToMessageId = message.replyToMessageId
|
||||
if (message.locationLat != null) compactMessage.locationLat = message.locationLat
|
||||
if (message.locationLng != null) compactMessage.locationLng = message.locationLng
|
||||
if (message.locationPoiname) compactMessage.locationPoiname = message.locationPoiname
|
||||
@@ -5830,6 +6030,20 @@ class ExportService {
|
||||
}
|
||||
}
|
||||
|
||||
const quotedReplyDisplay = await this.resolveQuotedReplyDisplayWithNames({
|
||||
content: msg.content,
|
||||
isGroup,
|
||||
displayNamePreference: options.displayNamePreference,
|
||||
getContact: getContactCached,
|
||||
groupNicknamesMap,
|
||||
cleanedMyWxid,
|
||||
rawMyWxid,
|
||||
myDisplayName: myInfo.displayName || cleanedMyWxid
|
||||
})
|
||||
if (quotedReplyDisplay) {
|
||||
enrichedContentValue = this.buildQuotedReplyText(quotedReplyDisplay)
|
||||
}
|
||||
|
||||
// 调试日志
|
||||
if (msg.localType === 3 || msg.localType === 47) {
|
||||
}
|
||||
@@ -6077,6 +6291,20 @@ class ExportService {
|
||||
}
|
||||
}
|
||||
|
||||
const quotedReplyDisplay = await this.resolveQuotedReplyDisplayWithNames({
|
||||
content: msg.content,
|
||||
isGroup,
|
||||
displayNamePreference: options.displayNamePreference,
|
||||
getContact: getContactCached,
|
||||
groupNicknamesMap,
|
||||
cleanedMyWxid,
|
||||
rawMyWxid,
|
||||
myDisplayName: myInfo.displayName || cleanedMyWxid
|
||||
})
|
||||
if (quotedReplyDisplay) {
|
||||
enrichedContentValue = this.buildQuotedReplyText(quotedReplyDisplay)
|
||||
}
|
||||
|
||||
appendRow(useCompactColumns
|
||||
? [
|
||||
i + 1,
|
||||
@@ -6430,6 +6658,20 @@ class ExportService {
|
||||
}
|
||||
}
|
||||
|
||||
const quotedReplyDisplay = await this.resolveQuotedReplyDisplayWithNames({
|
||||
content: msg.content,
|
||||
isGroup,
|
||||
displayNamePreference: options.displayNamePreference,
|
||||
getContact: getContactCached,
|
||||
groupNicknamesMap,
|
||||
cleanedMyWxid,
|
||||
rawMyWxid,
|
||||
myDisplayName: myInfo.displayName || cleanedMyWxid
|
||||
})
|
||||
if (quotedReplyDisplay) {
|
||||
enrichedContentValue = this.buildQuotedReplyText(quotedReplyDisplay)
|
||||
}
|
||||
|
||||
let senderRole: string
|
||||
let senderWxid: string
|
||||
let senderNickname: string
|
||||
@@ -6724,7 +6966,7 @@ class ExportService {
|
||||
})
|
||||
|
||||
const lines: string[] = []
|
||||
lines.push('id,MsgSvrID,type_name,is_sender,talker,msg,src,CreateTime')
|
||||
lines.push('id,MsgSvrID,ReplyToMsgSvrID,type_name,is_sender,talker,msg,src,CreateTime')
|
||||
const senderProfileCache = new Map<string, ExportDisplayProfile>()
|
||||
|
||||
for (let i = 0; i < totalMessages; i++) {
|
||||
@@ -6790,15 +7032,31 @@ class ExportService {
|
||||
msg.senderUsername,
|
||||
msg.isSend
|
||||
) || '')
|
||||
const quotedReplyDisplay = await this.resolveQuotedReplyDisplayWithNames({
|
||||
content: msg.content,
|
||||
isGroup,
|
||||
displayNamePreference: options.displayNamePreference,
|
||||
getContact: getContactCached,
|
||||
groupNicknamesMap,
|
||||
cleanedMyWxid,
|
||||
rawMyWxid,
|
||||
myDisplayName: myInfo.displayName || cleanedMyWxid
|
||||
})
|
||||
const finalMsgText = quotedReplyDisplay
|
||||
? this.buildQuotedReplyText(quotedReplyDisplay)
|
||||
: msgText
|
||||
const src = this.getWeCloneSource(msg, typeName, mediaItem)
|
||||
const platformMessageId = this.getExportPlatformMessageId(msg) || ''
|
||||
const replyToMessageId = this.getExportReplyToMessageId(msg.content) || ''
|
||||
|
||||
const row = [
|
||||
i + 1,
|
||||
i + 1,
|
||||
platformMessageId,
|
||||
replyToMessageId,
|
||||
typeName,
|
||||
msg.isSend ? 1 : 0,
|
||||
talker,
|
||||
msgText,
|
||||
finalMsgText,
|
||||
src,
|
||||
this.formatIsoTimestamp(msg.createTime)
|
||||
]
|
||||
@@ -7270,8 +7528,18 @@ class ExportService {
|
||||
|
||||
const timeText = this.formatTimestamp(msg.createTime)
|
||||
const typeName = this.getMessageTypeName(msg.localType)
|
||||
const quotedReplyDisplay = await this.resolveQuotedReplyDisplayWithNames({
|
||||
content: msg.content,
|
||||
isGroup,
|
||||
displayNamePreference: options.displayNamePreference,
|
||||
getContact: getContactCached,
|
||||
groupNicknamesMap,
|
||||
cleanedMyWxid,
|
||||
rawMyWxid,
|
||||
myDisplayName: myInfo.displayName || cleanedMyWxid
|
||||
})
|
||||
|
||||
let textContent = this.formatHtmlMessageText(
|
||||
let textContent = quotedReplyDisplay?.replyText || this.formatHtmlMessageText(
|
||||
msg.content,
|
||||
msg.localType,
|
||||
cleanedMyWxid,
|
||||
@@ -7302,7 +7570,7 @@ class ExportService {
|
||||
}
|
||||
}
|
||||
|
||||
const linkCard = this.extractHtmlLinkCard(msg.content, msg.localType)
|
||||
const linkCard = quotedReplyDisplay ? null : this.extractHtmlLinkCard(msg.content, msg.localType)
|
||||
|
||||
let mediaHtml = ''
|
||||
if (mediaItem?.kind === 'image') {
|
||||
@@ -7318,25 +7586,40 @@ class ExportService {
|
||||
mediaHtml = `<video class="message-media video" controls preload="metadata"${posterAttr} src="${this.escapeAttribute(encodeURI(mediaItem.relativePath))}"></video>`
|
||||
}
|
||||
|
||||
const textHtml = linkCard
|
||||
? `<div class="message-text"><a class="message-link-card" href="${this.escapeAttribute(linkCard.url)}" target="_blank" rel="noopener noreferrer">${this.renderTextWithEmoji(linkCard.title).replace(/\r?\n/g, '<br />')}</a></div>`
|
||||
: (textContent
|
||||
? `<div class="message-text">${this.renderTextWithEmoji(textContent).replace(/\r?\n/g, '<br />')}</div>`
|
||||
: '')
|
||||
const textHtml = quotedReplyDisplay
|
||||
? (() => {
|
||||
const quotedSenderHtml = quotedReplyDisplay.quotedSender
|
||||
? `<div class="quoted-sender">${this.escapeHtml(quotedReplyDisplay.quotedSender)}</div>`
|
||||
: ''
|
||||
const quotedPreviewHtml = `<div class="quoted-text">${this.renderTextWithEmoji(quotedReplyDisplay.quotedPreview).replace(/\r?\n/g, '<br />')}</div>`
|
||||
const replyTextHtml = textContent
|
||||
? `<div class="message-text">${this.renderTextWithEmoji(textContent).replace(/\r?\n/g, '<br />')}</div>`
|
||||
: ''
|
||||
return `<div class="quoted-message">${quotedSenderHtml}${quotedPreviewHtml}</div>${replyTextHtml}`
|
||||
})()
|
||||
: (linkCard
|
||||
? `<div class="message-text"><a class="message-link-card" href="${this.escapeAttribute(linkCard.url)}" target="_blank" rel="noopener noreferrer">${this.renderTextWithEmoji(linkCard.title).replace(/\r?\n/g, '<br />')}</a></div>`
|
||||
: (textContent
|
||||
? `<div class="message-text">${this.renderTextWithEmoji(textContent).replace(/\r?\n/g, '<br />')}</div>`
|
||||
: ''))
|
||||
const senderNameHtml = isGroup
|
||||
? `<div class="sender-name">${this.escapeHtml(resolvedSenderName)}</div>`
|
||||
: ''
|
||||
const timeHtml = `<div class="message-time">${this.escapeHtml(timeText)}</div>`
|
||||
const messageBody = `${timeHtml}${senderNameHtml}<div class="message-content">${mediaHtml}${textHtml}</div>`
|
||||
const platformMessageId = this.getExportPlatformMessageId(msg)
|
||||
const replyToMessageId = this.getExportReplyToMessageId(msg.content)
|
||||
|
||||
// Compact JSON object
|
||||
const itemObj = {
|
||||
const itemObj: Record<string, any> = {
|
||||
i: i + 1, // index
|
||||
t: msg.createTime, // timestamp
|
||||
s: isSenderMe ? 1 : 0, // isSend
|
||||
a: avatarHtml, // avatar HTML
|
||||
b: messageBody // body HTML
|
||||
}
|
||||
if (platformMessageId) itemObj.p = platformMessageId
|
||||
if (replyToMessageId) itemObj.r = replyToMessageId
|
||||
|
||||
writeBuf.push(JSON.stringify(itemObj))
|
||||
|
||||
@@ -7384,8 +7667,10 @@ class ExportService {
|
||||
// Render Item Function
|
||||
const renderItem = (item, index) => {
|
||||
const isSenderMe = item.s === 1;
|
||||
const platformIdAttr = item.p ? \` data-platform-message-id="\${item.p}"\` : '';
|
||||
const replyToAttr = item.r ? \` data-reply-to-message-id="\${item.r}"\` : '';
|
||||
return \`
|
||||
<div class="message \${isSenderMe ? 'sent' : 'received'}" data-index="\${item.i}">
|
||||
<div class="message \${isSenderMe ? 'sent' : 'received'}" data-index="\${item.i}"\${platformIdAttr}\${replyToAttr}>
|
||||
<div class="message-row">
|
||||
<div class="avatar">\${item.a}</div>
|
||||
<div class="bubble">
|
||||
|
||||
Reference in New Issue
Block a user