mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-18 03:57:02 +08:00
refactor(image-decrypt): 设置面板 UI 重构 + 测试步骤联动
页面拆分两个目录语义: - 上方「图片解密状态」的「图片资源目录」始终等于 chat 实时识别的默认目录(chat.getCurrentAccountRoot()), 不再被用户输入覆盖。 - 下方「图片密钥管理」移除「图片资源目录」输入框,只保留 XOR / AES 两个 Key。 该字段之前会被保存但实际不影响图片解密(数据库查询走默认根目录),保留只会让用户困惑。 UI 改动: - ImageKeyConfiguration:移除 resourceRoot 输入框,edit() 签名收窄为 xorKey | aesKey。 - ImageTestSection:测试结果由 3 列横排改为步骤条样式(pending / ok / fail / skipped), 任意一步 ✗ 后续步骤自动标记「跳过」并灰掉,前端语义上变成「要么都成功要么都失败」。 - AutoDetectImageKeySection:加一行小字提示「仅支持 WeChat 4.0,V3 及以下无法解析」。 后端改动: - testImageDecryption:解密失败时把 fileFound/decrypted/readable 统一置 false, 拆出「解密成功但不可读」中间态,让 UI 步骤联动准确反映。 - inspectImageDecryptionStatus:accountRoot 兜底改为 getCurrentAccountRoot() || config.resourceRoot, 让状态面板始终显示当前识别到的默认目录。 - sanitizeImageError:把 'no_image_message' / '300' / 'unsupported' / 'dat version' 关键字翻译成具体提示。 - useImageDecryptionController.autoDetect:错误信息原文透传,不再走 sanitizeImageError(后者会 把扫描阶段的「未找到 V2 模板文件」/「60 秒未找到 AES 密钥」等归类成「无法解析媒体文件」,掩盖真因)。 - image-key-config-service.save():不再写回 imageKeyRoot,下方输入框只用于校验,不再落盘。
This commit is contained in:
@@ -17,7 +17,9 @@ import { isWechatRunning } from './wechat-process-status'
|
||||
export async function inspectImageDecryptionStatus(
|
||||
config: ImageKeyConfigResult
|
||||
): Promise<ImageDecryptionStatus> {
|
||||
const accountRoot = chat.getCurrentAccountRoot() || config.resourceRoot
|
||||
// 状态面板的"图片资源目录"始终等于当前识别到的微信账号根目录;
|
||||
// 仅在微信未连接时回退到上次配置中的 resourceRoot,避免空白。
|
||||
const accountRoot = chat.getCurrentAccountRoot() || config.resourceRoot || ''
|
||||
const imageDirectoryFound = hasImageDirectory(accountRoot)
|
||||
const stickerCacheFound =
|
||||
fs.existsSync(path.join(accountRoot, 'cache')) ||
|
||||
@@ -65,7 +67,10 @@ export function testImageDecryption(
|
||||
.reverse()
|
||||
.find((message) => message.contentData?.type === 'image')
|
||||
if (!imageMessage || imageMessage.contentData?.type !== 'image') {
|
||||
return failure('NO_IMAGE_MESSAGE', '所选聊天最近没有可测试的图片消息')
|
||||
return failure(
|
||||
'NO_IMAGE_MESSAGE',
|
||||
'所选聊天最近 300 条消息内没有可测试的图片,请换一个含图片的会话'
|
||||
)
|
||||
}
|
||||
|
||||
const service = new ImageDecryptService(
|
||||
@@ -74,26 +79,51 @@ export function testImageDecryption(
|
||||
chat.getChatDb()?.getWcdb4Client()
|
||||
)
|
||||
const image = imageMessage.contentData
|
||||
let filePath = service.findImageFile(image.md5, image.datName, { allowThumbnail: false })
|
||||
// 测试时优先使用用户在下方"图片资源目录"输入框填写的目录;
|
||||
// 找不到再退回默认 accountDir。
|
||||
const testAccountDir = normalized.resourceRoot || undefined
|
||||
let filePath = service.findImageFile(image.md5, image.datName, {
|
||||
allowThumbnail: false,
|
||||
accountDir: testAccountDir
|
||||
})
|
||||
if (!filePath)
|
||||
filePath = service.findImageFile(image.md5, image.datName, { allowThumbnail: true })
|
||||
filePath = service.findImageFile(image.md5, image.datName, {
|
||||
allowThumbnail: true,
|
||||
accountDir: testAccountDir
|
||||
})
|
||||
if (!filePath) return failure('FILE_NOT_FOUND', '图片文件不存在')
|
||||
|
||||
const data = service.decryptImageToBase64(filePath)
|
||||
if (!data) {
|
||||
// 三步联动:解密失败 → fileFound/decrypted/readable 都为 false。
|
||||
return {
|
||||
...failure('DECRYPT_FAILED', '无法解析媒体文件'),
|
||||
fileFound: true
|
||||
success: false,
|
||||
code: 'DECRYPT_FAILED',
|
||||
error: '无法解析媒体文件',
|
||||
fileFound: false,
|
||||
decrypted: false,
|
||||
readable: false
|
||||
}
|
||||
}
|
||||
const readable = data.startsWith('data:image/')
|
||||
if (!readable) {
|
||||
// 三步联动:解密成功但字节流不可读 → 前一步打勾(确实找到了 dat),
|
||||
// 但 decrypted/readable 全为 false,让 UI 表达"找到但解析失败"。
|
||||
return {
|
||||
success: false,
|
||||
code: 'DECRYPT_FAILED',
|
||||
error: '图片解密结果不可读取',
|
||||
fileFound: true,
|
||||
decrypted: false,
|
||||
readable: false,
|
||||
isThumbnail: service.isThumbnailFile(filePath)
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: readable,
|
||||
code: readable ? undefined : 'DECRYPT_FAILED',
|
||||
error: readable ? undefined : '图片解密结果不可读取',
|
||||
success: true,
|
||||
fileFound: true,
|
||||
decrypted: true,
|
||||
readable,
|
||||
readable: true,
|
||||
isThumbnail: service.isThumbnailFile(filePath)
|
||||
}
|
||||
} catch {
|
||||
|
||||
@@ -92,9 +92,10 @@ export class ImageKeyConfigService {
|
||||
if (!result.success || !result.entry) {
|
||||
return { ...this.getEmptyConfig(), error: result.error || '图片密钥保存失败' }
|
||||
}
|
||||
// 注意:normalized.resourceRoot 不再写回 imageKeyRoot。
|
||||
// 下方的"图片资源目录"输入框仅用于本次手动测试,不再污染状态面板上方显示。
|
||||
saveSettings({
|
||||
...settings,
|
||||
imageKeyRoot: normalized.resourceRoot,
|
||||
imageXorKey: '',
|
||||
imageAesKey: '',
|
||||
imageKeyFallbackDisabled: false
|
||||
@@ -106,7 +107,9 @@ export class ImageKeyConfigService {
|
||||
encryptionAvailable: true,
|
||||
source: 'secure-storage',
|
||||
accountId: context.accountId,
|
||||
resourceRoot: normalized.resourceRoot,
|
||||
// 返回的 resourceRoot 始终是识别到的默认目录,与状态面板一致;
|
||||
// 不返回 normalized.resourceRoot,避免把用户输入的测试目录当成状态写回。
|
||||
resourceRoot: context.resourceRoot,
|
||||
xorKey: result.entry.xorKey,
|
||||
aesKey: result.entry.aesKey,
|
||||
updatedAt: result.entry.updatedAt
|
||||
|
||||
Reference in New Issue
Block a user