mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-24 23:06:51 +00:00
从密语同步了一下图片解密
This commit is contained in:
@@ -415,10 +415,16 @@ export class ImageDecryptService {
|
|||||||
if (imageDatName) this.cacheDatPath(accountDir, imageDatName, hardlinkPath)
|
if (imageDatName) this.cacheDatPath(accountDir, imageDatName, hardlinkPath)
|
||||||
return hardlinkPath
|
return hardlinkPath
|
||||||
}
|
}
|
||||||
// hardlink 找到的是缩略图,但要求高清图,直接返回 null,不再搜索
|
// hardlink 找到的是缩略图,但要求高清图
|
||||||
if (!allowThumbnail && isThumb) {
|
// 尝试在同一目录下查找高清图变体(快速查找,不遍历)
|
||||||
return null
|
const hdPath = this.findHdVariantInSameDir(hardlinkPath)
|
||||||
|
if (hdPath) {
|
||||||
|
this.cacheDatPath(accountDir, imageMd5, hdPath)
|
||||||
|
if (imageDatName) this.cacheDatPath(accountDir, imageDatName, hdPath)
|
||||||
|
return hdPath
|
||||||
}
|
}
|
||||||
|
// 没找到高清图,返回 null(不进行全局搜索)
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
this.logInfo('[ImageDecrypt] hardlink miss (md5)', { imageMd5 })
|
this.logInfo('[ImageDecrypt] hardlink miss (md5)', { imageMd5 })
|
||||||
if (imageDatName && this.looksLikeMd5(imageDatName) && imageDatName !== imageMd5) {
|
if (imageDatName && this.looksLikeMd5(imageDatName) && imageDatName !== imageMd5) {
|
||||||
@@ -431,9 +437,13 @@ export class ImageDecryptService {
|
|||||||
this.cacheDatPath(accountDir, imageDatName, fallbackPath)
|
this.cacheDatPath(accountDir, imageDatName, fallbackPath)
|
||||||
return fallbackPath
|
return fallbackPath
|
||||||
}
|
}
|
||||||
if (!allowThumbnail && isThumb) {
|
// 找到缩略图但要求高清图,尝试同目录查找高清图变体
|
||||||
return null
|
const hdPath = this.findHdVariantInSameDir(fallbackPath)
|
||||||
|
if (hdPath) {
|
||||||
|
this.cacheDatPath(accountDir, imageDatName, hdPath)
|
||||||
|
return hdPath
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
this.logInfo('[ImageDecrypt] hardlink miss (datName)', { imageDatName })
|
this.logInfo('[ImageDecrypt] hardlink miss (datName)', { imageDatName })
|
||||||
}
|
}
|
||||||
@@ -449,10 +459,13 @@ export class ImageDecryptService {
|
|||||||
this.cacheDatPath(accountDir, imageDatName, hardlinkPath)
|
this.cacheDatPath(accountDir, imageDatName, hardlinkPath)
|
||||||
return hardlinkPath
|
return hardlinkPath
|
||||||
}
|
}
|
||||||
// hardlink 找到的是缩略图,但要求高清图,直接返回 null
|
// hardlink 找到的是缩略图,但要求高清图
|
||||||
if (!allowThumbnail && isThumb) {
|
const hdPath = this.findHdVariantInSameDir(hardlinkPath)
|
||||||
return null
|
if (hdPath) {
|
||||||
|
this.cacheDatPath(accountDir, imageDatName, hdPath)
|
||||||
|
return hdPath
|
||||||
}
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
this.logInfo('[ImageDecrypt] hardlink miss (datName)', { imageDatName })
|
this.logInfo('[ImageDecrypt] hardlink miss (datName)', { imageDatName })
|
||||||
}
|
}
|
||||||
@@ -467,6 +480,9 @@ export class ImageDecryptService {
|
|||||||
const cached = this.resolvedCache.get(imageDatName)
|
const cached = this.resolvedCache.get(imageDatName)
|
||||||
if (cached && existsSync(cached)) {
|
if (cached && existsSync(cached)) {
|
||||||
if (allowThumbnail || !this.isThumbnailPath(cached)) return cached
|
if (allowThumbnail || !this.isThumbnailPath(cached)) return cached
|
||||||
|
// 缓存的是缩略图,尝试找高清图
|
||||||
|
const hdPath = this.findHdVariantInSameDir(cached)
|
||||||
|
if (hdPath) return hdPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -761,6 +777,17 @@ export class ImageDecryptService {
|
|||||||
|
|
||||||
const root = join(accountDir, 'msg', 'attach')
|
const root = join(accountDir, 'msg', 'attach')
|
||||||
if (!existsSync(root)) return null
|
if (!existsSync(root)) return null
|
||||||
|
|
||||||
|
// 优化1:快速概率性查找
|
||||||
|
// 包含:1. 基于文件名的前缀猜测 (旧版)
|
||||||
|
// 2. 基于日期的最近月份扫描 (新版无索引时)
|
||||||
|
const fastHit = await this.fastProbabilisticSearch(root, datName)
|
||||||
|
if (fastHit) {
|
||||||
|
this.resolvedCache.set(key, fastHit)
|
||||||
|
return fastHit
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优化2:兜底扫描 (异步非阻塞)
|
||||||
const found = await this.walkForDatInWorker(root, datName.toLowerCase(), 8, allowThumbnail, thumbOnly)
|
const found = await this.walkForDatInWorker(root, datName.toLowerCase(), 8, allowThumbnail, thumbOnly)
|
||||||
if (found) {
|
if (found) {
|
||||||
this.resolvedCache.set(key, found)
|
this.resolvedCache.set(key, found)
|
||||||
@@ -769,6 +796,134 @@ export class ImageDecryptService {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 基于文件名的哈希特征猜测可能的路径
|
||||||
|
* 包含:1. 微信旧版结构 filename.substr(0, 2)/...
|
||||||
|
* 2. 微信新版结构 msg/attach/{hash}/{YYYY-MM}/Img/filename
|
||||||
|
*/
|
||||||
|
private async fastProbabilisticSearch(root: string, datName: string): Promise<string | null> {
|
||||||
|
const { promises: fs } = require('fs')
|
||||||
|
const { join } = require('path')
|
||||||
|
|
||||||
|
try {
|
||||||
|
// --- 策略 A: 旧版路径猜测 (msg/attach/xx/yy/...) ---
|
||||||
|
const lowerName = datName.toLowerCase()
|
||||||
|
let baseName = lowerName
|
||||||
|
if (baseName.endsWith('.dat')) {
|
||||||
|
baseName = baseName.slice(0, -4)
|
||||||
|
if (baseName.endsWith('_t') || baseName.endsWith('.t') || baseName.endsWith('_hd')) {
|
||||||
|
baseName = baseName.slice(0, -3)
|
||||||
|
} else if (baseName.endsWith('_thumb')) {
|
||||||
|
baseName = baseName.slice(0, -6)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const candidates: string[] = []
|
||||||
|
if (/^[a-f0-9]{32}$/.test(baseName)) {
|
||||||
|
const dir1 = baseName.substring(0, 2)
|
||||||
|
const dir2 = baseName.substring(2, 4)
|
||||||
|
candidates.push(
|
||||||
|
join(root, dir1, dir2, datName),
|
||||||
|
join(root, dir1, dir2, 'Img', datName),
|
||||||
|
join(root, dir1, dir2, 'mg', datName),
|
||||||
|
join(root, dir1, dir2, 'Image', datName)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const path of candidates) {
|
||||||
|
try {
|
||||||
|
await fs.access(path)
|
||||||
|
return path
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- 策略 B: 新版 Session 哈希路径猜测 ---
|
||||||
|
try {
|
||||||
|
const entries = await fs.readdir(root, { withFileTypes: true })
|
||||||
|
const sessionDirs = entries
|
||||||
|
.filter((e: any) => e.isDirectory() && e.name.length === 32 && /^[a-f0-9]+$/i.test(e.name))
|
||||||
|
.map((e: any) => e.name)
|
||||||
|
|
||||||
|
if (sessionDirs.length === 0) return null
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const months: string[] = []
|
||||||
|
for (let i = 0; i < 2; i++) {
|
||||||
|
const d = new Date(now.getFullYear(), now.getMonth() - i, 1)
|
||||||
|
const mStr = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`
|
||||||
|
months.push(mStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetNames = [datName]
|
||||||
|
if (baseName !== lowerName) {
|
||||||
|
targetNames.push(`${baseName}.dat`)
|
||||||
|
targetNames.push(`${baseName}_t.dat`)
|
||||||
|
targetNames.push(`${baseName}_thumb.dat`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const batchSize = 20
|
||||||
|
for (let i = 0; i < sessionDirs.length; i += batchSize) {
|
||||||
|
const batch = sessionDirs.slice(i, i + batchSize)
|
||||||
|
const tasks = batch.map(async (sessDir: string) => {
|
||||||
|
for (const month of months) {
|
||||||
|
const subDirs = ['Img', 'Image']
|
||||||
|
for (const sub of subDirs) {
|
||||||
|
const dirPath = join(root, sessDir, month, sub)
|
||||||
|
try { await fs.access(dirPath) } catch { continue }
|
||||||
|
for (const name of targetNames) {
|
||||||
|
const p = join(dirPath, name)
|
||||||
|
try { await fs.access(p); return p } catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
const results = await Promise.all(tasks)
|
||||||
|
const hit = results.find(r => r !== null)
|
||||||
|
if (hit) return hit
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
} catch { }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在同一目录下查找高清图变体
|
||||||
|
* 缩略图: xxx_t.dat -> 高清图: xxx_h.dat 或 xxx.dat
|
||||||
|
*/
|
||||||
|
private findHdVariantInSameDir(thumbPath: string): string | null {
|
||||||
|
try {
|
||||||
|
const dir = dirname(thumbPath)
|
||||||
|
const fileName = basename(thumbPath).toLowerCase()
|
||||||
|
|
||||||
|
// 提取基础名称(去掉 _t.dat 或 .t.dat)
|
||||||
|
let baseName = fileName
|
||||||
|
if (baseName.endsWith('_t.dat')) {
|
||||||
|
baseName = baseName.slice(0, -6)
|
||||||
|
} else if (baseName.endsWith('.t.dat')) {
|
||||||
|
baseName = baseName.slice(0, -6)
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试查找高清图变体
|
||||||
|
const variants = [
|
||||||
|
`${baseName}_h.dat`,
|
||||||
|
`${baseName}.h.dat`,
|
||||||
|
`${baseName}.dat`
|
||||||
|
]
|
||||||
|
|
||||||
|
for (const variant of variants) {
|
||||||
|
const variantPath = join(dir, variant)
|
||||||
|
if (existsSync(variantPath)) {
|
||||||
|
return variantPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
private async searchDatFileInDir(
|
private async searchDatFileInDir(
|
||||||
dirPath: string,
|
dirPath: string,
|
||||||
datName: string,
|
datName: string,
|
||||||
|
|||||||
Reference in New Issue
Block a user