fix: 修复媒体导出与 HTML 聊天档案体验

- 修复打包版图片、语音解析不可用,内置 FFmpeg 与必要运行时依赖
- 修复 HTML 导出原图查找、缩略图回退及增量档案媒体解析问题
- 修复本人昵称在软件和 HTML 导出中显示为微信号的问题,并兼容旧档案昵称迁移
- 修复 HTML 语音播放器超出消息气泡的问题
- 优化 HTML 双向滚动加载,大量消息时最多渲染 240 条,避免页面卡顿
- 移除图片解密页面中手动配置 FFmpeg 的相关提示
- 补充图片解密、语音运行时、打包资源和 HTML 导出相关测试
This commit is contained in:
Wxw-Gu
2026-08-04 17:03:07 +08:00
parent c6587c517a
commit 0a3d930298
24 changed files with 949 additions and 515 deletions
+31
View File
@@ -428,6 +428,37 @@ function isRawContactName(contact: Contact): boolean {
return false
}
function accountRootCandidates(accountRoot: string): Set<string> {
const directory = path.basename(normalizeRoot(accountRoot))
const suffixMatch = directory.match(/^(.+)_([a-zA-Z0-9]{4})$/)
return new Set([directory, suffixMatch?.[1] || ''].filter(Boolean))
}
export function mergeCachedSelfInfo(accountRoot: string, self: CachedSelfInfo): CachedSelfInfo {
const cache = readStartupCacheFile(accountRoot)
if (!cache) return self
const identifiers = accountRootCandidates(accountRoot)
if (self.wxid) identifiers.add(self.wxid)
const isRawSelfName = (value?: string): boolean => {
const name = String(value || '').trim()
return !name || name === '我' || identifiers.has(name)
}
if (!isRawSelfName(self.nickname)) return self
const cachedContact = cache.contacts.find(
(contact) => identifiers.has(contact.m_nsUsrName) && !isRawContactName(contact)
)
const cachedNickname = !isRawSelfName(cache.self?.nickname)
? cache.self?.nickname
: cachedContact?.m_nsNickName
if (!cachedNickname) return self
return {
...self,
nickname: cachedNickname,
avatar: self.avatar || cache.self?.avatar || cachedContact?.avatar
}
}
export function mergeCachedContactAvatars(accountRoot: string, contacts: Contact[]): Contact[] {
const cache = readStartupCacheFile(accountRoot)
if (!cache?.contacts.length) return contacts
+12
View File
@@ -565,6 +565,18 @@ export function getSelfAccountInfo(): SelfAccountInfo | null {
}
}
export async function getSelfAccountInfoAsync(): Promise<SelfAccountInfo | null> {
const current = dbRef
if (!current) return null
try {
await current.getWcdb4Client().getSessionsAsync({ hydrateDisplayNames: true })
} catch {
// Nickname hydration is best-effort; the synchronous fallback still returns the account id.
}
if (dbRef !== current) return getSelfAccountInfo()
return getSelfAccountInfo()
}
export function testConnection(key: string, accountRoot?: string): DatabaseKeyValidationResult {
const probeKey = key.replace(/^0x/i, '').trim()
if (!/^[0-9a-f]{64}$/i.test(probeKey)) {
@@ -124,14 +124,18 @@ export async function testImageDecryption(
(await service.findImageFileAsync(image.md5, image.datName, {
allowThumbnail: false,
accountDir: testAccountDir,
sessionId: imageMessage.sessionId
sessionId: imageMessage.sessionId,
sessionMd5: request.userMd5,
createTime: imageMessage.createTime
})) || undefined
if (!filePath) {
filePath =
(await service.findImageFileAsync(image.md5, image.datName, {
allowThumbnail: true,
accountDir: testAccountDir,
sessionId: imageMessage.sessionId
sessionId: imageMessage.sessionId,
sessionMd5: request.userMd5,
createTime: imageMessage.createTime
})) || undefined
}
if (!filePath) return finish(failure('FILE_NOT_FOUND', '图片文件不存在'))