mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-20 14:39:25 +08:00
修复
This commit is contained in:
@@ -3438,9 +3438,10 @@ class ChatService {
|
||||
const datPath = await this.findDatFile(actualAccountDir, baseName, sessionId)
|
||||
if (!datPath) return { success: false, error: '未找到图片源文件 (.dat)' }
|
||||
|
||||
// 4. 获取解密密钥
|
||||
const xorKeyRaw = this.configService.get('imageXorKey')
|
||||
const aesKeyRaw = this.configService.get('imageAesKey') || msg.aesKey
|
||||
// 4. 获取解密密钥(优先使用当前 wxid 对应的密钥)
|
||||
const imageKeys = this.configService.getImageKeysForCurrentWxid()
|
||||
const xorKeyRaw = imageKeys.xorKey
|
||||
const aesKeyRaw = imageKeys.aesKey || msg.aesKey
|
||||
|
||||
if (!xorKeyRaw) return { success: false, error: '未配置图片 XOR 密钥,请在设置中自动获取' }
|
||||
|
||||
|
||||
@@ -637,6 +637,27 @@ export class ConfigService {
|
||||
|
||||
// === 工具方法 ===
|
||||
|
||||
/**
|
||||
* 获取当前 wxid 对应的图片密钥,优先从 wxidConfigs 中取,找不到则回退到全局配置
|
||||
*/
|
||||
getImageKeysForCurrentWxid(): { xorKey: unknown; aesKey: string } {
|
||||
const wxid = this.get('myWxid')
|
||||
if (wxid) {
|
||||
const wxidConfigs = this.get('wxidConfigs')
|
||||
const cfg = wxidConfigs?.[wxid]
|
||||
if (cfg && (cfg.imageXorKey !== undefined || cfg.imageAesKey)) {
|
||||
return {
|
||||
xorKey: cfg.imageXorKey ?? this.get('imageXorKey'),
|
||||
aesKey: cfg.imageAesKey ?? this.get('imageAesKey')
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
xorKey: this.get('imageXorKey'),
|
||||
aesKey: this.get('imageAesKey')
|
||||
}
|
||||
}
|
||||
|
||||
getCacheBasePath(): string {
|
||||
return join(app.getPath('userData'), 'cache')
|
||||
}
|
||||
|
||||
@@ -240,7 +240,9 @@ export class ImageDecryptService {
|
||||
}
|
||||
}
|
||||
|
||||
const xorKeyRaw = this.configService.get('imageXorKey') as unknown
|
||||
// 优先使用当前 wxid 对应的密钥,找不到则回退到全局配置
|
||||
const imageKeys = this.configService.getImageKeysForCurrentWxid()
|
||||
const xorKeyRaw = imageKeys.xorKey
|
||||
// 支持十六进制格式(如 0x53)和十进制格式
|
||||
let xorKey: number
|
||||
if (typeof xorKeyRaw === 'number') {
|
||||
@@ -257,7 +259,7 @@ export class ImageDecryptService {
|
||||
return { success: false, error: '未配置图片解密密钥' }
|
||||
}
|
||||
|
||||
const aesKeyRaw = this.configService.get('imageAesKey')
|
||||
const aesKeyRaw = imageKeys.aesKey
|
||||
const aesKey = this.resolveAesKey(aesKeyRaw)
|
||||
|
||||
this.logInfo('开始解密DAT文件', { datPath, xorKey, hasAesKey: !!aesKey })
|
||||
|
||||
@@ -4,6 +4,7 @@ import { existsSync, copyFileSync, mkdirSync } from 'fs'
|
||||
import { execFile, spawn } from 'child_process'
|
||||
import { promisify } from 'util'
|
||||
import os from 'os'
|
||||
import crypto from 'crypto'
|
||||
|
||||
const execFileAsync = promisify(execFile)
|
||||
|
||||
@@ -637,7 +638,16 @@ export class KeyService {
|
||||
return { success: false, error: '获取密钥超时', logs }
|
||||
}
|
||||
|
||||
// --- Image Key (通过 DLL 从缓存目录直接获取) ---
|
||||
// --- Image Key (通过 DLL 从缓存目录获取 code,用前端 wxid 计算密钥) ---
|
||||
|
||||
private cleanWxid(wxid: string): string {
|
||||
// 截断到第二个下划线: wxid_g4pshorcc0r529_da6c → wxid_g4pshorcc0r529
|
||||
const first = wxid.indexOf('_')
|
||||
if (first === -1) return wxid
|
||||
const second = wxid.indexOf('_', first + 1)
|
||||
if (second === -1) return wxid
|
||||
return wxid.substring(0, second)
|
||||
}
|
||||
|
||||
async autoGetImageKey(
|
||||
manualDir?: string,
|
||||
@@ -664,41 +674,51 @@ export class KeyService {
|
||||
return { success: false, error: '解析密钥数据失败' }
|
||||
}
|
||||
|
||||
// 从 manualDir 中提取 wxid 用于精确匹配
|
||||
// 前端传入的格式是 dbPath/wxid_xxx_1234,取最后一段目录名再清理后缀
|
||||
let targetWxid: string | null = null
|
||||
// 从任意账号提取 code 列表(code 来自 kvcomm,与 wxid 无关,所有账号都一样)
|
||||
const accounts: any[] = parsed.accounts ?? []
|
||||
if (!accounts.length || !accounts[0]?.keys?.length) {
|
||||
return { success: false, error: '未找到有效的密钥码(kvcomm 缓存为空)' }
|
||||
}
|
||||
|
||||
const codes: number[] = accounts[0].keys.map((k: any) => k.code)
|
||||
console.log('[ImageKey] codes:', codes, 'DLL wxids:', accounts.map((a: any) => a.wxid))
|
||||
|
||||
// 从 manualDir 提取前端已配置好的正确 wxid
|
||||
// 格式: "D:\weixin\xwechat_files\wxid_xxx_1234" → "wxid_xxx_1234"
|
||||
let targetWxid = ''
|
||||
if (manualDir) {
|
||||
const dirName = manualDir.replace(/[\\/]+$/, '').split(/[\\/]/).pop() ?? ''
|
||||
// 与 DLL 的 CleanWxid 逻辑一致:wxid_a_b_c → wxid_a
|
||||
const parts = dirName.split('_')
|
||||
if (parts.length >= 3 && parts[0] === 'wxid') {
|
||||
targetWxid = `${parts[0]}_${parts[1]}`
|
||||
} else if (dirName.startsWith('wxid_')) {
|
||||
if (dirName.startsWith('wxid_')) {
|
||||
targetWxid = dirName
|
||||
}
|
||||
}
|
||||
|
||||
const accounts: any[] = parsed.accounts ?? []
|
||||
if (!accounts.length) {
|
||||
return { success: false, error: '未找到有效的密钥组合' }
|
||||
if (!targetWxid) {
|
||||
// 无法从 manualDir 提取 wxid,回退到 DLL 发现的第一个
|
||||
targetWxid = accounts[0].wxid
|
||||
console.log('[ImageKey] 无法从 manualDir 提取 wxid,使用 DLL 发现的:', targetWxid)
|
||||
}
|
||||
|
||||
// 优先匹配 wxid,找不到则回退到第一个
|
||||
const matchedAccount = targetWxid
|
||||
? (accounts.find((a: any) => a.wxid === targetWxid) ?? accounts[0])
|
||||
: accounts[0]
|
||||
// CleanWxid: 截断到第二个下划线,与 xkey 算法一致
|
||||
const cleanedWxid = this.cleanWxid(targetWxid)
|
||||
console.log('[ImageKey] wxid:', targetWxid, '→ cleaned:', cleanedWxid)
|
||||
|
||||
if (!matchedAccount?.keys?.length) {
|
||||
return { success: false, error: '未找到有效的密钥组合' }
|
||||
}
|
||||
// 用 cleanedWxid + code 本地计算密钥
|
||||
// xorKey = code & 0xFF
|
||||
// aesKey = MD5(code.toString() + cleanedWxid).substring(0, 16)
|
||||
const code = codes[0]
|
||||
const xorKey = code & 0xFF
|
||||
const dataToHash = code.toString() + cleanedWxid
|
||||
const md5Full = crypto.createHash('md5').update(dataToHash).digest('hex')
|
||||
const aesKey = md5Full.substring(0, 16)
|
||||
|
||||
const firstKey = matchedAccount.keys[0]
|
||||
onProgress?.(`密钥获取成功 (wxid: ${matchedAccount.wxid}, code: ${firstKey.code})`)
|
||||
onProgress?.(`密钥获取成功 (wxid: ${targetWxid}, code: ${code})`)
|
||||
console.log('[ImageKey] 计算结果: xorKey=', xorKey, 'aesKey=', aesKey)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
xorKey: firstKey.xorKey,
|
||||
aesKey: firstKey.aesKey
|
||||
xorKey,
|
||||
aesKey
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user