mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-04-26 07:26:46 +00:00
fix: 迁移图片资源保留原始dat
This commit is contained in:
@@ -7,7 +7,6 @@ import * as tar from 'tar'
|
|||||||
import { ConfigService } from './config'
|
import { ConfigService } from './config'
|
||||||
import { wcdbService } from './wcdbService'
|
import { wcdbService } from './wcdbService'
|
||||||
import { expandHomePath } from '../utils/pathUtils'
|
import { expandHomePath } from '../utils/pathUtils'
|
||||||
import { decryptDatViaNative, encryptDatViaNative, type NativeDatMeta } from './nativeImageDecrypt'
|
|
||||||
|
|
||||||
type BackupDbKind = 'session' | 'contact' | 'emoticon' | 'message' | 'media' | 'sns' | 'hardlink'
|
type BackupDbKind = 'session' | 'contact' | 'emoticon' | 'message' | 'media' | 'sns' | 'hardlink'
|
||||||
type BackupPhase = 'preparing' | 'scanning' | 'exporting' | 'packing' | 'inspecting' | 'restoring' | 'done' | 'failed'
|
type BackupPhase = 'preparing' | 'scanning' | 'exporting' | 'packing' | 'inspecting' | 'restoring' | 'done' | 'failed'
|
||||||
@@ -48,7 +47,6 @@ interface BackupResourceEntry {
|
|||||||
targetRelativePath: string
|
targetRelativePath: string
|
||||||
ext?: string
|
ext?: string
|
||||||
size?: number
|
size?: number
|
||||||
datMeta?: NativeDatMeta
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BackupManifest {
|
interface BackupManifest {
|
||||||
@@ -290,27 +288,6 @@ export class BackupService {
|
|||||||
return suffixMatch ? suffixMatch[1] : trimmed
|
return suffixMatch ? suffixMatch[1] : trimmed
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseImageXorKey(value: unknown): number {
|
|
||||||
if (typeof value === 'number') return value
|
|
||||||
const text = String(value ?? '').trim()
|
|
||||||
if (!text) return Number.NaN
|
|
||||||
return text.toLowerCase().startsWith('0x') ? parseInt(text, 16) : parseInt(text, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
private getImageKeysForWxid(wxid: string): { xorKey: number; aesKey?: string } | null {
|
|
||||||
const wxidConfigs = this.configService.get('wxidConfigs') || {}
|
|
||||||
const candidates = this.buildWxidCandidates(wxid)
|
|
||||||
const matchedKey = Object.keys(wxidConfigs).find((key) => {
|
|
||||||
const cleanKey = this.cleanAccountDirName(key).toLowerCase()
|
|
||||||
return candidates.some(candidate => cleanKey === candidate.toLowerCase())
|
|
||||||
})
|
|
||||||
const cfg = matchedKey ? wxidConfigs[matchedKey] : undefined
|
|
||||||
const xorKey = this.parseImageXorKey(cfg?.imageXorKey ?? this.configService.get('imageXorKey'))
|
|
||||||
if (!Number.isFinite(xorKey)) return null
|
|
||||||
const aesKey = String(cfg?.imageAesKey ?? this.configService.get('imageAesKey') ?? '').trim()
|
|
||||||
return { xorKey, aesKey: aesKey || undefined }
|
|
||||||
}
|
|
||||||
|
|
||||||
private async listFilesForArchive(root: string, rel = '', state = { visited: 0 }): Promise<string[]> {
|
private async listFilesForArchive(root: string, rel = '', state = { visited: 0 }): Promise<string[]> {
|
||||||
const dir = join(root, rel)
|
const dir = join(root, rel)
|
||||||
const files: string[] = []
|
const files: string[] = []
|
||||||
@@ -626,11 +603,9 @@ export class BackupService {
|
|||||||
manifest: BackupManifest
|
manifest: BackupManifest
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const accountDir = dirname(connected.dbStorage)
|
const accountDir = dirname(connected.dbStorage)
|
||||||
const keys = this.getImageKeysForWxid(connected.wxid)
|
|
||||||
const imagesDir = join(stagingDir, 'resources', 'images')
|
const imagesDir = join(stagingDir, 'resources', 'images')
|
||||||
const imagePaths = await this.listChatImageDatFiles(accountDir)
|
const imagePaths = await this.listChatImageDatFiles(accountDir)
|
||||||
if (imagePaths.length === 0) return
|
if (imagePaths.length === 0) return
|
||||||
if (!keys) throw new Error('存在图片资源,但未配置图片解密密钥')
|
|
||||||
|
|
||||||
mkdirSync(imagesDir, { recursive: true })
|
mkdirSync(imagesDir, { recursive: true })
|
||||||
const resources: BackupResourceEntry[] = []
|
const resources: BackupResourceEntry[] = []
|
||||||
@@ -641,18 +616,16 @@ export class BackupService {
|
|||||||
if (!relativeTarget) continue
|
if (!relativeTarget) continue
|
||||||
emitImageProgress({
|
emitImageProgress({
|
||||||
phase: 'exporting',
|
phase: 'exporting',
|
||||||
message: '正在解密图片资源',
|
message: '正在打包图片资源',
|
||||||
current: index + 1,
|
current: index + 1,
|
||||||
total: imagePaths.length,
|
total: imagePaths.length,
|
||||||
detail: relativeTarget
|
detail: relativeTarget
|
||||||
})
|
})
|
||||||
const decrypted = decryptDatViaNative(sourcePath, keys.xorKey, keys.aesKey)
|
const archivePath = toArchivePath(join('resources', 'images', relativeTarget))
|
||||||
if (!decrypted) continue
|
|
||||||
const archivePath = toArchivePath(join('resources', 'images', `${relativeTarget}${decrypted.ext || '.bin'}`))
|
|
||||||
const outputPath = join(stagingDir, archivePath)
|
const outputPath = join(stagingDir, archivePath)
|
||||||
mkdirSync(dirname(outputPath), { recursive: true })
|
await this.stagePlainResource(sourcePath, outputPath)
|
||||||
await writeFile(outputPath, decrypted.data)
|
|
||||||
const stem = basename(sourcePath).replace(/\.dat$/i, '').toLowerCase()
|
const stem = basename(sourcePath).replace(/\.dat$/i, '').toLowerCase()
|
||||||
|
const stat = statSync(sourcePath)
|
||||||
resources.push({
|
resources.push({
|
||||||
kind: 'image',
|
kind: 'image',
|
||||||
id: relativeTarget,
|
id: relativeTarget,
|
||||||
@@ -660,9 +633,7 @@ export class BackupService {
|
|||||||
sourceFileName: basename(sourcePath),
|
sourceFileName: basename(sourcePath),
|
||||||
archivePath,
|
archivePath,
|
||||||
targetRelativePath: relativeTarget,
|
targetRelativePath: relativeTarget,
|
||||||
ext: decrypted.ext || undefined,
|
size: stat.size
|
||||||
size: decrypted.data.length,
|
|
||||||
datMeta: decrypted.meta
|
|
||||||
})
|
})
|
||||||
if (index % 20 === 0) await delay()
|
if (index % 20 === 0) await delay()
|
||||||
}
|
}
|
||||||
@@ -930,13 +901,6 @@ export class BackupService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const accountDir = dirname(connected.dbStorage)
|
const accountDir = dirname(connected.dbStorage)
|
||||||
const imageKeys = imageByPath.size > 0
|
|
||||||
? this.getImageKeysForWxid(connected.wxid || String(manifest.source?.wxid || '').trim())
|
|
||||||
: null
|
|
||||||
if (imageByPath.size > 0 && !imageKeys) {
|
|
||||||
throw new Error('备份包包含图片资源,但目标账号未配置图片加密密钥')
|
|
||||||
}
|
|
||||||
|
|
||||||
let current = startCurrent
|
let current = startCurrent
|
||||||
let skipped = 0
|
let skipped = 0
|
||||||
const pending: Promise<void>[] = []
|
const pending: Promise<void>[] = []
|
||||||
@@ -957,35 +921,26 @@ export class BackupService {
|
|||||||
|
|
||||||
const image = imageByPath.get(entryPath)
|
const image = imageByPath.get(entryPath)
|
||||||
if (image) {
|
if (image) {
|
||||||
const tempPath = this.resolveStagingPath(extractDir, entryPath)
|
|
||||||
const targetPath = this.resolveTargetResourcePath(accountDir, image.targetRelativePath)
|
const targetPath = this.resolveTargetResourcePath(accountDir, image.targetRelativePath)
|
||||||
if (!tempPath || !targetPath) {
|
if (!targetPath) {
|
||||||
skipped += 1
|
skipped += 1
|
||||||
entry.resume()
|
entry.resume()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const task = this.writeTarEntryToFile(entry, tempPath).then(async () => {
|
current += 1
|
||||||
current += 1
|
emitRestoreProgress({
|
||||||
emitRestoreProgress({
|
phase: 'restoring',
|
||||||
phase: 'restoring',
|
message: '正在写回图片资源',
|
||||||
message: '正在加密并写回图片资源',
|
current,
|
||||||
current,
|
total,
|
||||||
total,
|
detail: image.md5 || image.targetRelativePath
|
||||||
detail: image.md5 || image.targetRelativePath
|
|
||||||
})
|
|
||||||
if (existsSync(targetPath)) {
|
|
||||||
skipped += 1
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const encrypted = encryptDatViaNative(tempPath, imageKeys!.xorKey, imageKeys!.aesKey, image.datMeta)
|
|
||||||
if (!encrypted) {
|
|
||||||
skipped += 1
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mkdirSync(dirname(targetPath), { recursive: true })
|
|
||||||
await writeFile(targetPath, encrypted)
|
|
||||||
})
|
})
|
||||||
pending.push(task)
|
if (existsSync(targetPath)) {
|
||||||
|
skipped += 1
|
||||||
|
entry.resume()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pending.push(this.writeTarEntryToFile(entry, targetPath))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user