Handle more message types in exports

This commit is contained in:
QingXiao
2026-01-23 23:53:33 +08:00
parent d37f53e120
commit 388923257b
2 changed files with 61 additions and 55 deletions

View File

@@ -321,6 +321,33 @@ class ExportService {
} }
return '[其他消息]' return '[其他消息]'
} }
if (localType === 42) {
const normalized = this.normalizeAppMessageContent(safeContent)
const nickname =
this.extractXmlValue(normalized, 'nickname') ||
this.extractXmlValue(normalized, 'displayname') ||
this.extractXmlValue(normalized, 'name')
return nickname ? `[名片]${nickname}` : '[名片]'
}
if (localType === 43) {
const normalized = this.normalizeAppMessageContent(safeContent)
const lengthValue =
this.extractXmlValue(normalized, 'playlength') ||
this.extractXmlValue(normalized, 'playLength') ||
this.extractXmlValue(normalized, 'length') ||
this.extractXmlValue(normalized, 'duration')
const seconds = lengthValue ? this.parseDurationSeconds(lengthValue) : null
return seconds ? `[视频]${seconds}s` : '[视频]'
}
if (localType === 48) {
const normalized = this.normalizeAppMessageContent(safeContent)
const location =
this.extractXmlValue(normalized, 'label') ||
this.extractXmlValue(normalized, 'poiname') ||
this.extractXmlValue(normalized, 'poiName') ||
this.extractXmlValue(normalized, 'name')
return location ? `[定位]${location}` : '[定位]'
}
if (localType === 10000 || localType === 266287972401) { if (localType === 10000 || localType === 266287972401) {
return this.cleanSystemMessage(safeContent) return this.cleanSystemMessage(safeContent)
} }
@@ -331,10 +358,31 @@ class ExportService {
const typeMatch = /<type>(\d+)<\/type>/i.exec(normalized) const typeMatch = /<type>(\d+)<\/type>/i.exec(normalized)
const subType = typeMatch ? parseInt(typeMatch[1], 10) : 0 const subType = typeMatch ? parseInt(typeMatch[1], 10) : 0
const title = this.extractXmlValue(normalized, 'title') || this.extractXmlValue(normalized, 'appname') const title = this.extractXmlValue(normalized, 'title') || this.extractXmlValue(normalized, 'appname')
if (subType === 3 || normalized.includes('<musicurl') || normalized.includes('<songname')) {
const songName = this.extractXmlValue(normalized, 'songname') || title || '音乐'
return `[音乐]${songName}`
}
if (subType === 6) { if (subType === 6) {
const fileName = this.extractXmlValue(normalized, 'filename') || title || '文件' const fileName = this.extractXmlValue(normalized, 'filename') || title || '文件'
return `[文件]${fileName}` return `[文件]${fileName}`
} }
if (title.includes('转账') || normalized.includes('transfer')) {
const amount = this.extractAmountFromText(
[
title,
this.extractXmlValue(normalized, 'des'),
this.extractXmlValue(normalized, 'money'),
this.extractXmlValue(normalized, 'amount'),
this.extractXmlValue(normalized, 'fee')
]
.filter(Boolean)
.join(' ')
)
return amount ? `[转账]${amount}` : '[转账]'
}
if (title.includes('红包') || normalized.includes('hongbao')) {
return `[红包]${title || '微信红包'}`
}
if (subType === 19 || normalized.includes('<recorditem')) { if (subType === 19 || normalized.includes('<recorditem')) {
const forwardName = const forwardName =
this.extractXmlValue(normalized, 'nickname') || this.extractXmlValue(normalized, 'nickname') ||
@@ -356,6 +404,19 @@ class ExportService {
return '[其他消息]' return '[其他消息]'
} }
private parseDurationSeconds(value: string): number | null {
const numeric = Number(value)
if (!Number.isFinite(numeric) || numeric <= 0) return null
if (numeric >= 1000) return Math.round(numeric / 1000)
return Math.round(numeric)
}
private extractAmountFromText(text: string): string | null {
if (!text) return null
const match = /([¥¥]\s*\d+(?:\.\d+)?|\d+(?:\.\d+)?)/.exec(text)
return match ? match[1].replace(/\s+/g, '') : null
}
private stripSenderPrefix(content: string): string { private stripSenderPrefix(content: string): string {
return content.replace(/^[\s]*([a-zA-Z0-9_-]+):(?!\/\/)/, '') return content.replace(/^[\s]*([a-zA-Z0-9_-]+):(?!\/\/)/, '')
} }

View File

@@ -61,7 +61,6 @@ function SettingsPage() {
const [exportDefaultMedia, setExportDefaultMedia] = useState(false) const [exportDefaultMedia, setExportDefaultMedia] = useState(false)
const [exportDefaultVoiceAsText, setExportDefaultVoiceAsText] = useState(true) const [exportDefaultVoiceAsText, setExportDefaultVoiceAsText] = useState(true)
const [exportDefaultExcelCompactColumns, setExportDefaultExcelCompactColumns] = useState(true) const [exportDefaultExcelCompactColumns, setExportDefaultExcelCompactColumns] = useState(true)
const [exportDefaultTxtColumns, setExportDefaultTxtColumns] = useState<string[]>(['index', 'time', 'senderRole', 'messageType', 'content'])
const [isLoading, setIsLoadingState] = useState(false) const [isLoading, setIsLoadingState] = useState(false)
const [isTesting, setIsTesting] = useState(false) const [isTesting, setIsTesting] = useState(false)
@@ -142,8 +141,6 @@ function SettingsPage() {
const savedExportDefaultMedia = await configService.getExportDefaultMedia() const savedExportDefaultMedia = await configService.getExportDefaultMedia()
const savedExportDefaultVoiceAsText = await configService.getExportDefaultVoiceAsText() const savedExportDefaultVoiceAsText = await configService.getExportDefaultVoiceAsText()
const savedExportDefaultExcelCompactColumns = await configService.getExportDefaultExcelCompactColumns() const savedExportDefaultExcelCompactColumns = await configService.getExportDefaultExcelCompactColumns()
const savedExportDefaultTxtColumns = await configService.getExportDefaultTxtColumns()
const defaultTxtColumns = ['index', 'time', 'senderRole', 'messageType', 'content']
if (savedKey) setDecryptKey(savedKey) if (savedKey) setDecryptKey(savedKey)
if (savedPath) setDbPath(savedPath) if (savedPath) setDbPath(savedPath)
@@ -161,11 +158,6 @@ function SettingsPage() {
setExportDefaultMedia(savedExportDefaultMedia ?? false) setExportDefaultMedia(savedExportDefaultMedia ?? false)
setExportDefaultVoiceAsText(savedExportDefaultVoiceAsText ?? true) setExportDefaultVoiceAsText(savedExportDefaultVoiceAsText ?? true)
setExportDefaultExcelCompactColumns(savedExportDefaultExcelCompactColumns ?? true) setExportDefaultExcelCompactColumns(savedExportDefaultExcelCompactColumns ?? true)
setExportDefaultTxtColumns(
savedExportDefaultTxtColumns && savedExportDefaultTxtColumns.length > 0
? savedExportDefaultTxtColumns
: defaultTxtColumns
)
// 如果语言列表为空,保存默认值 // 如果语言列表为空,保存默认值
if (!savedTranscribeLanguages || savedTranscribeLanguages.length === 0) { if (!savedTranscribeLanguages || savedTranscribeLanguages.length === 0) {
@@ -174,9 +166,6 @@ function SettingsPage() {
await configService.setTranscribeLanguages(defaultLanguages) await configService.setTranscribeLanguages(defaultLanguages)
} }
if (!savedExportDefaultTxtColumns || savedExportDefaultTxtColumns.length === 0) {
await configService.setExportDefaultTxtColumns(defaultTxtColumns)
}
if (savedWhisperModelDir) setWhisperModelDir(savedWhisperModelDir) if (savedWhisperModelDir) setWhisperModelDir(savedWhisperModelDir)
} catch (e) { } catch (e) {
@@ -911,16 +900,6 @@ function SettingsPage() {
{ value: 'compact', label: '精简列', desc: '序号、时间、发送者身份、消息类型、内容' }, { value: 'compact', label: '精简列', desc: '序号、时间、发送者身份、消息类型、内容' },
{ value: 'full', label: '完整列', desc: '含发送者昵称/微信ID/备注' } { value: 'full', label: '完整列', desc: '含发送者昵称/微信ID/备注' }
] ]
const exportTxtColumnOptions = [
{ value: 'index', label: '序号' },
{ value: 'time', label: '时间' },
{ value: 'senderRole', label: '发送者身份' },
{ value: 'messageType', label: '消息类型' },
{ value: 'content', label: '内容' },
{ value: 'senderNickname', label: '发送者昵称' },
{ value: 'senderWxid', label: '发送者微信ID' },
{ value: 'senderRemark', label: '发送者备注' }
]
const getOptionLabel = (options: { value: string; label: string }[], value: string) => { const getOptionLabel = (options: { value: string; label: string }[], value: string) => {
return options.find((option) => option.value === value)?.label ?? value return options.find((option) => option.value === value)?.label ?? value
@@ -1097,40 +1076,6 @@ function SettingsPage() {
</div> </div>
</div> </div>
<div className="form-group">
<label>TXT </label>
<span className="form-hint"> Excel </span>
<div className="language-checkboxes">
{exportTxtColumnOptions.map((column) => {
const checked = exportDefaultTxtColumns.includes(column.value)
return (
<label key={column.value} className="language-checkbox">
<input
type="checkbox"
checked={checked}
onChange={async (e) => {
const enabled = e.target.checked
const nextColumns = enabled
? [...exportDefaultTxtColumns, column.value]
: exportDefaultTxtColumns.filter((value) => value !== column.value)
if (nextColumns.length === 0) {
showMessage('至少选择一个 TXT 导出栏目', false)
return
}
setExportDefaultTxtColumns(nextColumns)
await configService.setExportDefaultTxtColumns(nextColumns)
showMessage('已更新 TXT 导出栏目', true)
}}
/>
<div className="checkbox-custom">
<Check size={14} />
<span>{column.label}</span>
</div>
</label>
)
})}
</div>
</div>
</div> </div>
) )
} }