支持自动化条件导出;优化引导页面提示;支持快速添加账号

This commit is contained in:
cc
2026-04-12 23:37:26 +08:00
parent 69a598f196
commit 86daa8ef06
19 changed files with 3765 additions and 688 deletions

View File

@@ -71,6 +71,7 @@ interface ConfigSchema {
quoteLayout: 'quote-top' | 'quote-bottom'
wordCloudExcludeWords: string[]
exportWriteLayout: 'A' | 'B' | 'C'
exportAutomationTaskMap: Record<string, unknown>
// AI 见解
aiModelApiBaseUrl: string
@@ -185,6 +186,7 @@ export class ConfigService {
quoteLayout: 'quote-top',
wordCloudExcludeWords: [],
exportWriteLayout: 'A',
exportAutomationTaskMap: {},
aiModelApiBaseUrl: '',
aiModelApiKey: '',
aiModelApiModel: 'gpt-4o-mini',

View File

@@ -9,7 +9,7 @@ import crypto from 'crypto'
const execFileAsync = promisify(execFile)
type DbKeyResult = { success: boolean; key?: string; error?: string; logs?: string[] }
type ImageKeyResult = { success: boolean; xorKey?: number; aesKey?: string; error?: string }
type ImageKeyResult = { success: boolean; xorKey?: number; aesKey?: string; verified?: boolean; error?: string }
export class KeyService {
private readonly isMac = process.platform === 'darwin'
@@ -814,7 +814,7 @@ export class KeyService {
if (!this.verifyDerivedAesKey(aesKey, verifyCiphertext)) continue
onProgress?.(`密钥获取成功 (wxid: ${candidateWxid}, code: ${code})`)
console.log('[ImageKey] 校验命中: wxid=', candidateWxid, 'code=', code)
return { success: true, xorKey, aesKey }
return { success: true, xorKey, aesKey, verified: true }
}
}
return { success: false, error: '缓存 code 与当前账号 wxid 未匹配,请确认账号目录后重试,或使用内存扫描' }
@@ -826,7 +826,7 @@ export class KeyService {
const { xorKey, aesKey } = this.deriveImageKeys(fallbackCode, fallbackWxid)
onProgress?.(`密钥获取成功 (wxid: ${fallbackWxid}, code: ${fallbackCode})`)
console.log('[ImageKey] 回退计算: wxid=', fallbackWxid, 'code=', fallbackCode)
return { success: true, xorKey, aesKey }
return { success: true, xorKey, aesKey, verified: false }
}
// --- 内存扫描备选方案(融合 Dart+Python 优点)---

View File

@@ -3,6 +3,7 @@ import { join } from 'path'
import { existsSync, readdirSync, statSync, readFileSync } from 'fs'
import { execFile, exec, spawn } from 'child_process'
import { promisify } from 'util'
import crypto from 'crypto'
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
@@ -10,7 +11,7 @@ const execFileAsync = promisify(execFile)
const execAsync = promisify(exec)
type DbKeyResult = { success: boolean; key?: string; error?: string; logs?: string[] }
type ImageKeyResult = { success: boolean; xorKey?: number; aesKey?: string; error?: string }
type ImageKeyResult = { success: boolean; xorKey?: number; aesKey?: string; verified?: boolean; error?: string }
export class KeyServiceLinux {
private sudo: any
@@ -243,7 +244,14 @@ export class KeyServiceLinux {
if (account && account.keys && account.keys.length > 0) {
onProgress?.(`已找到匹配的图片密钥 (wxid: ${account.wxid})`);
const keyObj = account.keys[0]
return { success: true, xorKey: keyObj.xorKey, aesKey: keyObj.aesKey }
const aesKey = String(keyObj.aesKey || '')
const verified = await this.verifyImageKeyByTemplate(accountPath, aesKey)
if (verified === true) {
onProgress?.('缓存密钥校验成功,已确认可用')
} else if (verified === false) {
onProgress?.('已从缓存计算密钥,但未通过本地模板校验')
}
return { success: true, xorKey: keyObj.xorKey, aesKey, verified: verified === true }
}
return { success: false, error: '未在缓存中找到匹配的图片密钥' }
} catch (err: any) {
@@ -251,6 +259,35 @@ export class KeyServiceLinux {
}
}
private async verifyImageKeyByTemplate(accountPath: string | undefined, aesKey: string): Promise<boolean | null> {
const normalizedPath = String(accountPath || '').trim()
if (!normalizedPath || !aesKey || aesKey.length < 16 || !existsSync(normalizedPath)) return null
try {
const template = await this._findTemplateData(normalizedPath, 32)
if (!template.ciphertext) return null
return this.verifyDerivedAesKey(aesKey, template.ciphertext)
} catch {
return null
}
}
private verifyDerivedAesKey(aesKey: string, ciphertext: Buffer): boolean {
try {
if (!aesKey || aesKey.length < 16 || ciphertext.length !== 16) return false
const decipher = crypto.createDecipheriv('aes-128-ecb', Buffer.from(aesKey, 'ascii').subarray(0, 16), null)
decipher.setAutoPadding(false)
const dec = Buffer.concat([decipher.update(ciphertext), decipher.final()])
if (dec[0] === 0xFF && dec[1] === 0xD8 && dec[2] === 0xFF) return true
if (dec[0] === 0x89 && dec[1] === 0x50 && dec[2] === 0x4E && dec[3] === 0x47) return true
if (dec[0] === 0x52 && dec[1] === 0x49 && dec[2] === 0x46 && dec[3] === 0x46) return true
if (dec[0] === 0x77 && dec[1] === 0x78 && dec[2] === 0x67 && dec[3] === 0x66) return true
if (dec[0] === 0x47 && dec[1] === 0x49 && dec[2] === 0x46) return true
return false
} catch {
return false
}
}
public async autoGetImageKeyByMemoryScan(
accountPath: string,
onProgress?: (msg: string) => void

View File

@@ -7,7 +7,7 @@ import crypto from 'crypto'
import { homedir } from 'os'
type DbKeyResult = { success: boolean; key?: string; error?: string; logs?: string[] }
type ImageKeyResult = { success: boolean; xorKey?: number; aesKey?: string; error?: string }
type ImageKeyResult = { success: boolean; xorKey?: number; aesKey?: string; verified?: boolean; error?: string }
const execFileAsync = promisify(execFile)
export class KeyServiceMac {
@@ -647,7 +647,7 @@ export class KeyServiceMac {
const { xorKey, aesKey } = this.deriveImageKeys(code, candidateWxid)
if (!this.verifyDerivedAesKey(aesKey, template.ciphertext)) continue
onStatus?.(`密钥获取成功 (wxid: ${candidateWxid}, code: ${code})`)
return { success: true, xorKey, aesKey }
return { success: true, xorKey, aesKey, verified: true }
}
}
}
@@ -662,7 +662,7 @@ export class KeyServiceMac {
const fallbackCode = codes[0]
const { xorKey, aesKey } = this.deriveImageKeys(fallbackCode, fallbackWxid)
onStatus?.(`密钥获取成功 (wxid: ${fallbackWxid}, code: ${fallbackCode})`)
return { success: true, xorKey, aesKey }
return { success: true, xorKey, aesKey, verified: false }
} catch (e: any) {
return { success: false, error: `自动获取图片密钥失败: ${e.message}` }
}