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