mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
feat: add progress
This commit is contained in:
@@ -4,7 +4,6 @@ import { existsSync, readdirSync, readFileSync, statSync, copyFileSync, mkdirSyn
|
|||||||
import { execFile, spawn } from 'child_process'
|
import { execFile, spawn } from 'child_process'
|
||||||
import { promisify } from 'util'
|
import { promisify } from 'util'
|
||||||
import { Worker } from 'worker_threads'
|
import { Worker } from 'worker_threads'
|
||||||
import crypto from 'crypto'
|
|
||||||
import os from 'os'
|
import os from 'os'
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile)
|
const execFileAsync = promisify(execFile)
|
||||||
@@ -936,7 +935,8 @@ export class KeyService {
|
|||||||
if (msg.type === 'progress') {
|
if (msg.type === 'progress') {
|
||||||
totalScanned += msg.scanned
|
totalScanned += msg.scanned
|
||||||
const percent = ((totalScanned / totalCombinations) * 100).toFixed(1)
|
const percent = ((totalScanned / totalCombinations) * 100).toFixed(1)
|
||||||
onProgress?.(`多核爆破中: 已尝试 ${(totalScanned / 10000).toFixed(0)} 万次 (${percent}%)`)
|
// 优化文案,并确保包含 (xx.x%) 供前端解析
|
||||||
|
onProgress?.(`多核爆破引擎运行中:已扫描 ${(totalScanned / 10000).toFixed(0)} 万个密钥空间 (${percent}%)`)
|
||||||
} else if (msg.type === 'success' && !resolved) {
|
} else if (msg.type === 'success' && !resolved) {
|
||||||
resolved = true
|
resolved = true
|
||||||
cleanup()
|
cleanup()
|
||||||
@@ -966,31 +966,28 @@ export class KeyService {
|
|||||||
manualDir?: string,
|
manualDir?: string,
|
||||||
onProgress?: (message: string) => void
|
onProgress?: (message: string) => void
|
||||||
): Promise<ImageKeyResult> {
|
): Promise<ImageKeyResult> {
|
||||||
onProgress?.('正在定位微信账号目录...')
|
onProgress?.('正在定位微信账号数据目录...')
|
||||||
const accountDir = this.resolveAccountDir(manualDir)
|
const accountDir = this.resolveAccountDir(manualDir)
|
||||||
if (!accountDir) return { success: false, error: '未找到微信账号目录' }
|
if (!accountDir) return { success: false, error: '未找到微信账号目录' }
|
||||||
|
|
||||||
// 精确提取 wxid,直接剥离 _f1c4 类似的 4 位十六进制校验码
|
|
||||||
let wxid = basename(accountDir)
|
let wxid = basename(accountDir)
|
||||||
wxid = wxid.replace(/_[0-9a-fA-F]{4}$/, '')
|
wxid = wxid.replace(/_[0-9a-fA-F]{4}$/, '')
|
||||||
|
|
||||||
onProgress?.('正在收集模板文件...')
|
onProgress?.('正在收集并分析加密模板文件...')
|
||||||
const templateFiles = this.findTemplateDatFiles(accountDir)
|
const templateFiles = this.findTemplateDatFiles(accountDir)
|
||||||
if (!templateFiles.length) return { success: false, error: '未找到模板文件' }
|
if (!templateFiles.length) return { success: false, error: '未找到模板文件' }
|
||||||
|
|
||||||
onProgress?.('正在计算 XOR 密钥...')
|
onProgress?.('正在计算特征 XOR 密钥...')
|
||||||
const xorKey = this.getXorKey(templateFiles)
|
const xorKey = this.getXorKey(templateFiles)
|
||||||
if (xorKey == null) return { success: false, error: '无法计算 XOR 密钥' }
|
if (xorKey == null) return { success: false, error: '无法计算 XOR 密钥' }
|
||||||
|
|
||||||
onProgress?.('正在读取加密模板数据...')
|
onProgress?.('正在读取加密模板区块...')
|
||||||
const ciphertexts = this.getCiphertextsFromTemplate(templateFiles)
|
const ciphertexts = this.getCiphertextsFromTemplate(templateFiles)
|
||||||
if (ciphertexts.length === 0) return { success: false, error: '无法读取加密模板数据' }
|
if (ciphertexts.length === 0) return { success: false, error: '无法读取加密模板数据' }
|
||||||
|
|
||||||
// 提示收集到的样本数量
|
onProgress?.(`成功提取 ${ciphertexts.length} 个特征样本,准备交叉校验...`)
|
||||||
onProgress?.(`提取到 ${ciphertexts.length} 个特征样本,开始进行交叉校验...`)
|
onProgress?.(`准备启动 ${os.cpus().length || 4} 线程并发爆破引擎 (基于 wxid: ${wxid})...`)
|
||||||
|
|
||||||
onProgress?.(`正在利用多核爆破 AES 密钥 (基于 wxid: ${wxid})...`)
|
|
||||||
// 注意这里传入的是 ciphertexts 数组
|
|
||||||
const aesKey = await this.bruteForceAesKey(xorKey, wxid, ciphertexts, (msg) => {
|
const aesKey = await this.bruteForceAesKey(xorKey, wxid, ciphertexts, (msg) => {
|
||||||
onProgress?.(msg)
|
onProgress?.(msg)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -2173,3 +2173,70 @@
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.brute-force-progress {
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 12px;
|
||||||
|
animation: slideUp 0.3s ease;
|
||||||
|
|
||||||
|
.status-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
|
||||||
|
.status-text {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
|
// 增加文字呼吸灯效果,表明正在运行
|
||||||
|
animation: pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.percent {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--primary);
|
||||||
|
font-weight: 700;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
background: var(--bg-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.fill {
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, var(--primary) 0%, color-mix(in srgb, var(--primary) 60%, white) 100%);
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
// 流光扫过的高亮特效
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
transparent,
|
||||||
|
rgba(255, 255, 255, 0.3),
|
||||||
|
transparent
|
||||||
|
);
|
||||||
|
animation: progress-shimmer 1.5s infinite linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -82,6 +82,8 @@ function SettingsPage() {
|
|||||||
const exportExcelColumnsDropdownRef = useRef<HTMLDivElement>(null)
|
const exportExcelColumnsDropdownRef = useRef<HTMLDivElement>(null)
|
||||||
const exportConcurrencyDropdownRef = useRef<HTMLDivElement>(null)
|
const exportConcurrencyDropdownRef = useRef<HTMLDivElement>(null)
|
||||||
const [cachePath, setCachePath] = useState('')
|
const [cachePath, setCachePath] = useState('')
|
||||||
|
const [imageKeyProgress, setImageKeyProgress] = useState(0)
|
||||||
|
const [imageKeyPercent, setImageKeyPercent] = useState<number | null>(null)
|
||||||
|
|
||||||
const [logEnabled, setLogEnabled] = useState(false)
|
const [logEnabled, setLogEnabled] = useState(false)
|
||||||
const [whisperModelName, setWhisperModelName] = useState('base')
|
const [whisperModelName, setWhisperModelName] = useState('base')
|
||||||
@@ -222,8 +224,28 @@ function SettingsPage() {
|
|||||||
const removeDb = window.electronAPI.key.onDbKeyStatus((payload: { message: string; level: number }) => {
|
const removeDb = window.electronAPI.key.onDbKeyStatus((payload: { message: string; level: number }) => {
|
||||||
setDbKeyStatus(payload.message)
|
setDbKeyStatus(payload.message)
|
||||||
})
|
})
|
||||||
const removeImage = window.electronAPI.key.onImageKeyStatus((payload: { message: string }) => {
|
|
||||||
setImageKeyStatus(payload.message)
|
const removeImage = window.electronAPI.key.onImageKeyStatus((payload: { message: string, percent?: number }) => {
|
||||||
|
let msg = payload.message;
|
||||||
|
let pct = payload.percent;
|
||||||
|
|
||||||
|
// 如果后端没有显式传 percent,则用正则从字符串中提取如 "(12.5%)"
|
||||||
|
if (pct === undefined) {
|
||||||
|
const match = msg.match(/\(([\d.]+)%\)/);
|
||||||
|
if (match) {
|
||||||
|
pct = parseFloat(match[1]);
|
||||||
|
// 将百分比从文本中剥离,让 UI 更清爽
|
||||||
|
msg = msg.replace(/\s*\([\d.]+%\)/, '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setImageKeyStatus(msg);
|
||||||
|
if (pct !== undefined) {
|
||||||
|
setImageKeyPercent(pct);
|
||||||
|
} else if (msg.includes('启动多核') || msg.includes('定位') || msg.includes('准备')) {
|
||||||
|
// 预热阶段
|
||||||
|
setImageKeyPercent(0);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
return () => {
|
return () => {
|
||||||
removeDb?.()
|
removeDb?.()
|
||||||
@@ -745,15 +767,18 @@ function SettingsPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleAutoGetImageKey = async () => {
|
const handleAutoGetImageKey = async () => {
|
||||||
if (isFetchingImageKey) return
|
if (isFetchingImageKey) return;
|
||||||
if (!dbPath) {
|
if (!dbPath) {
|
||||||
showMessage('请先选择数据库目录', false)
|
showMessage('请先选择数据库目录', false);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
setIsFetchingImageKey(true)
|
setIsFetchingImageKey(true);
|
||||||
setImageKeyStatus('正在准备获取图片密钥...')
|
setImageKeyPercent(0)
|
||||||
|
setImageKeyStatus('正在初始化多核爆破引擎...');
|
||||||
|
setImageKeyProgress(0); // 重置进度
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const accountPath = wxid ? `${dbPath}/${wxid}` : dbPath
|
const accountPath = wxid ? `${dbPath}/${wxid}` : dbPath;
|
||||||
const result = await window.electronAPI.key.autoGetImageKey(accountPath)
|
const result = await window.electronAPI.key.autoGetImageKey(accountPath)
|
||||||
if (result.success && result.aesKey) {
|
if (result.success && result.aesKey) {
|
||||||
if (typeof result.xorKey === 'number') {
|
if (typeof result.xorKey === 'number') {
|
||||||
@@ -1352,11 +1377,19 @@ function SettingsPage() {
|
|||||||
<Plug size={14} /> {isFetchingImageKey ? '获取中...' : '自动获取图片密钥'}
|
<Plug size={14} /> {isFetchingImageKey ? '获取中...' : '自动获取图片密钥'}
|
||||||
</button>
|
</button>
|
||||||
{isFetchingImageKey ? (
|
{isFetchingImageKey ? (
|
||||||
<div className="form-hint status-text" style={{ color: '#007bff', fontWeight: 'bold', marginTop: '6px' }}>
|
<div className="brute-force-progress">
|
||||||
{imageKeyStatus || '正在启动多核爆破引擎...'}
|
<div className="status-header">
|
||||||
|
<span className="status-text">{imageKeyStatus || '正在启动多核爆破引擎...'}</span>
|
||||||
|
{imageKeyPercent !== null && <span className="percent">{imageKeyPercent.toFixed(1)}%</span>}
|
||||||
|
</div>
|
||||||
|
{imageKeyPercent !== null && (
|
||||||
|
<div className="progress-bar-container">
|
||||||
|
<div className="fill" style={{ width: `${imageKeyPercent}%` }}></div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
imageKeyStatus && <div className="form-hint status-text">{imageKeyStatus}</div>
|
imageKeyStatus && <div className="form-hint status-text" style={{ marginTop: '8px' }}>{imageKeyStatus}</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user