From e0d2712393aaa445b54deda28532e34cfe62ea57 Mon Sep 17 00:00:00 2001 From: ILoveBingLu Date: Tue, 3 Mar 2026 02:15:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=A2=9E=E5=BC=BA=E8=AE=BE=E7=BD=AE?= =?UTF-8?q?=E5=92=8C=E6=AC=A2=E8=BF=8E=E9=A1=B5=E9=9D=A2=E7=9A=84=E8=B4=A6?= =?UTF-8?q?=E5=8F=B7=E9=AA=8C=E8=AF=81=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=94=A8=E6=88=B7=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + electron/services/annualReportService.ts | 66 ++++++++++++-- electron/services/chatService.ts | 34 ++++++-- package.json | 2 +- src/pages/AnnualReportPage.tsx | 24 ++++-- src/pages/AnnualReportWindow.tsx | 29 ++++--- src/pages/SettingsPage.tsx | 58 ++++++++++--- src/pages/WelcomePage.tsx | 104 +++++++++++++++++++---- 8 files changed, 260 insertions(+), 58 deletions(-) diff --git a/.gitignore b/.gitignore index a4774ec..ad3fb3e 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,4 @@ native-dlls MyCoolInstaller resources/whisper xkey +skills diff --git a/electron/services/annualReportService.ts b/electron/services/annualReportService.ts index b102a60..ec0f127 100644 --- a/electron/services/annualReportService.ts +++ b/electron/services/annualReportService.ts @@ -79,6 +79,27 @@ export interface AnnualReportData { class AnnualReportService { private configService: ConfigService private messageDbCache: Map = new Map() + private readonly systemAccounts = new Set([ + 'medianote', + 'floatbottle', + 'qmessage', + 'qqmail', + 'fmessage', + 'weixin', + 'newsapp', + 'notification_messages', + 'weixinreminder', + 'masssendapp', + 'qqsync', + 'facebookapp', + 'feedsapp', + 'voip', + 'blogapp', + 'gmailapp', + 'linkedinplugin', + 'appbrand_notify_message', + 'appbrandcustomerservicemsg' + ]) constructor() { this.configService = new ConfigService() @@ -211,6 +232,32 @@ class AnnualReportService { return crypto.createHash('md5').update(username).digest('hex') } + private shouldExcludeAnnualSession(username: string): boolean { + if (!username) return true + const u = username.toLowerCase().trim() + if (!u) return true + + // 群聊、公众号、文件传输助手 + if (u.includes('@chatroom')) return true + if (u.startsWith('gh_')) return true + if (u === 'filehelper') return true + + // 已知系统账号 + if (this.systemAccounts.has(u)) return true + + // 邮箱/提醒类 + if (u.includes('qqmail') || u.includes('mail')) return true + if (u.includes('reminder') || u.includes('notify')) return true + + return false + } + + private extractTableHash(tableName: string): string | null { + const match = tableName.match(/msg_([0-9a-f]{32})/i) + if (match?.[1]) return match[1].toLowerCase() + return null + } + private hasName2IdTable(db: Database.Database): boolean { try { const result = db.prepare( @@ -270,7 +317,7 @@ class AnnualReportService { const tables = db.prepare(` SELECT name FROM sqlite_master - WHERE type='table' AND name LIKE 'Msg_%' + WHERE type='table' AND lower(name) LIKE 'msg_%' `).all() as { name: string }[] for (const { name: tableName } of tables) { @@ -345,17 +392,19 @@ class AnnualReportService { .map(s => s.username) .filter(u => { const uLower = u.toLowerCase() - return uLower !== wxidLower && uLower !== cleanedWxidLower + return uLower !== wxidLower && uLower !== cleanedWxidLower && !this.shouldExcludeAnnualSession(u) }) // 构建 hash -> username 映射 const hashToUsername = new Map() for (const username of privateUsernames) { - hashToUsername.set(this.getTableHash(username), username) + hashToUsername.set(this.getTableHash(username).toLowerCase(), username) } - const startTime = Math.floor(new Date(year, 0, 1).getTime() / 1000) - const endTime = Math.floor(new Date(year, 11, 31, 23, 59, 59).getTime() / 1000) + const isAllTime = year <= 0 + const reportYear = isAllTime ? 0 : year + const startTime = isAllTime ? 0 : Math.floor(new Date(year, 0, 1).getTime() / 1000) + const endTime = isAllTime ? Math.floor(Date.now() / 1000) : Math.floor(new Date(year, 11, 31, 23, 59, 59).getTime() / 1000) // 统计数据 let totalMessages = 0 @@ -387,12 +436,13 @@ class AnnualReportService { const tables = db.prepare(` SELECT name FROM sqlite_master - WHERE type='table' AND name LIKE 'Msg_%' + WHERE type='table' AND lower(name) LIKE 'msg_%' `).all() as { name: string }[] for (const { name: tableName } of tables) { // 从表名提取 hash,查找对应的 sessionId - const tableHash = tableName.replace('Msg_', '') + const tableHash = this.extractTableHash(tableName) + if (!tableHash) continue const sessionId = hashToUsername.get(tableHash) if (!sessionId) continue // 不是私聊表 @@ -803,7 +853,7 @@ class AnnualReportService { .map(([phrase, count]) => ({ phrase, count })) const reportData: AnnualReportData = { - year, + year: reportYear, totalMessages, totalFriends: contactStats.size, coreFriends, diff --git a/electron/services/chatService.ts b/electron/services/chatService.ts index 7bb9b8a..694ab60 100644 --- a/electron/services/chatService.ts +++ b/electron/services/chatService.ts @@ -840,22 +840,44 @@ class ChatService extends EventEmitter { return hash } + /** + * 从消息表名中提取会话 hash(兼容大小写与后缀) + */ + private extractTableHash(tableName: string): string | null { + const match = tableName.match(/msg_([0-9a-f]{32})/i) + if (match?.[1]) return match[1].toLowerCase() + return null + } + /** * 在消息数据库中查找会话的消息表(带缓存) */ private findMessageTable(db: Database.Database, sessionId: string): string | null { try { const tables = db.prepare( - "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'Msg_%'" + "SELECT name FROM sqlite_master WHERE type='table' AND lower(name) LIKE 'msg_%'" ).all() as any[] - const hash = this.getTableNameHash(sessionId) + const hash = this.getTableNameHash(sessionId).toLowerCase() for (const table of tables) { const name = table.name as string - if (name.includes(hash)) { + + // 优先精确提取 hash 匹配 + const tableHash = this.extractTableHash(name) + if (tableHash && tableHash === hash) { return name } + + // 兜底兼容:历史表名规则可能不完全一致,采用大小写无关包含匹配 + if (name.toLowerCase().includes(hash)) { + return name + } + } + + if (tables.length > 0) { + const sample = tables.slice(0, 8).map(t => t.name).join(', ') + console.warn(`[ChatService] 未匹配到消息表: session=${sessionId}, hash=${hash}, tables=${tables.length}, sample=[${sample}]`) } } catch { } @@ -3386,9 +3408,9 @@ class ChatService extends EventEmitter { if (!db) continue // 查找所有消息表 - const tables = db.prepare( - "SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'Msg_%'" - ).all() as any[] + const tables = db.prepare( + "SELECT name FROM sqlite_master WHERE type='table' AND lower(name) LIKE 'msg_%'" + ).all() as any[] for (const table of tables) { const tableName = table.name as string diff --git a/package.json b/package.json index 893ee7a..a8d7832 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ciphertalk", - "version": "2.2.9", + "version": "2.2.10", "description": "密语 - 微信聊天记录查看工具", "author": "ILoveBingLu", "license": "CC-BY-NC-SA-4.0", diff --git a/src/pages/AnnualReportPage.tsx b/src/pages/AnnualReportPage.tsx index 3fe0f0b..540218a 100644 --- a/src/pages/AnnualReportPage.tsx +++ b/src/pages/AnnualReportPage.tsx @@ -2,9 +2,11 @@ import { useState, useEffect } from 'react' import { Calendar, Loader2, Sparkles } from 'lucide-react' import './AnnualReportPage.scss' +type YearOption = number | 'all' + function AnnualReportPage() { const [availableYears, setAvailableYears] = useState([]) - const [selectedYear, setSelectedYear] = useState(null) + const [selectedYear, setSelectedYear] = useState(null) const [isLoading, setIsLoading] = useState(true) const [isGenerating, setIsGenerating] = useState(false) @@ -31,7 +33,8 @@ function AnnualReportPage() { if (!selectedYear) return setIsGenerating(true) try { - await window.electronAPI.window.openAnnualReportWindow(selectedYear) + const yearParam = selectedYear === 'all' ? 0 : selectedYear + await window.electronAPI.window.openAnnualReportWindow(yearParam) } catch (e) { console.error('生成报告失败:', e) } finally { @@ -58,6 +61,15 @@ function AnnualReportPage() { ) } + const yearOptions: YearOption[] = availableYears.length > 0 + ? ['all', ...availableYears] + : [] + + const getYearLabel = (value: YearOption | null) => { + if (!value) return '' + return value === 'all' ? '全部时间' : `${value}` + } + return (
@@ -65,14 +77,14 @@ function AnnualReportPage() {

选择年份,生成你的微信聊天年度回顾

- {availableYears.map(year => ( + {yearOptions.map(year => (
setSelectedYear(year)} > - {year} - + {year === 'all' ? '全部' : year} + {year === 'all' ? '时间' : '年'}
))}
@@ -90,7 +102,7 @@ function AnnualReportPage() { ) : ( <> - 生成 {selectedYear} 年度报告 + 生成 {getYearLabel(selectedYear)} 年度报告 )} diff --git a/src/pages/AnnualReportWindow.tsx b/src/pages/AnnualReportWindow.tsx index affecdb..9a7cccc 100644 --- a/src/pages/AnnualReportWindow.tsx +++ b/src/pages/AnnualReportWindow.tsx @@ -361,6 +361,11 @@ function AnnualReportWindow() { return `${Math.round(seconds / 3600)}小时` } + const formatYearLabel = (value: number, withSuffix: boolean = true) => { + if (value === 0) return '全部时间' + return withSuffix ? `${value}年` : `${value}` + } + // 获取可用的板块列表 const getAvailableSections = (): SectionInfo[] => { if (!reportData) return [] @@ -623,7 +628,8 @@ function AnnualReportWindow() { const finalDataUrl = outputCanvas.toDataURL('image/png') const link = document.createElement('a') - link.download = `${reportData?.year}年度报告.png` + const yearFilePrefix = reportData ? formatYearLabel(reportData.year, false) : '' + link.download = `${yearFilePrefix}年度报告.png` link.href = finalDataUrl document.body.appendChild(link) link.click() @@ -669,22 +675,24 @@ function AnnualReportWindow() { // 单张图片直接下载,多张打包成 zip if (exportedImages.length === 1) { const link = document.createElement('a') - link.download = `${reportData?.year}年度报告_${exportedImages[0].name}.png` + const yearFilePrefix = reportData ? formatYearLabel(reportData.year, false) : '' + link.download = `${yearFilePrefix}年度报告_${exportedImages[0].name}.png` link.href = exportedImages[0].data link.click() } else { setExportProgress('正在打包...') const zip = new JSZip() + const yearFilePrefix = reportData ? formatYearLabel(reportData.year, false) : '' for (const img of exportedImages) { // 从 data URL 提取 base64 数据 const base64Data = img.data.split(',')[1] - zip.file(`${reportData?.year}年度报告_${img.name}.png`, base64Data, { base64: true }) + zip.file(`${yearFilePrefix}年度报告_${img.name}.png`, base64Data, { base64: true }) } const blob = await zip.generateAsync({ type: 'blob' }) const link = document.createElement('a') - link.download = `${reportData?.year}年度报告_分模块.zip` + link.download = `${yearFilePrefix}年度报告_分模块.zip` link.href = URL.createObjectURL(blob) link.click() URL.revokeObjectURL(link.href) @@ -753,6 +761,7 @@ function AnnualReportWindow() { } const { year, totalMessages, totalFriends, coreFriends, monthlyTopFriends, peakDay, longestStreak, activityHeatmap, midnightKing, selfAvatarUrl, mutualFriend, socialInitiative, responseSpeed, topPhrases } = reportData + const yearTitle = formatYearLabel(year) const topFriend = coreFriends[0] const mostActive = getMostActiveTime(activityHeatmap.data) @@ -855,10 +864,10 @@ function AnnualReportWindow() { {/* 封面 */}
CipherTalk · ANNUAL REPORT
-
{year}
+
{year === 0 ? 'ALL' : year}

微信聊天报告


-

时光匆匆,转眼又是一年
让我们一起回顾这一年的点点滴滴

+

时光匆匆,回望这段时间
让我们一起回顾你们的点点滴滴

{/* 年度概览 */} @@ -895,7 +904,7 @@ function AnnualReportWindow() { {/* 月度好友 */}
月度好友
-

{year}年月度好友

+

{yearTitle}月度好友

根据12个月的聊天习惯
每个月陪你最多的人

{monthlyTopFriends.map((m, i) => ( @@ -1036,9 +1045,9 @@ function AnnualReportWindow() { {topPhrases && topPhrases.length > 0 && (
年度常用语
-

你在{year}年的年度常用语

+

你在{yearTitle}的高频表达

- 这一年,你说得最多的是: + 这段时间,你说得最多的是:
{topPhrases.slice(0, 3).map(p => p.phrase).join('、')} @@ -1103,7 +1112,7 @@ function AnnualReportWindow() { 我们总是在向前走,却很少有机会回头看看
愿新的一年,所有期待,皆有回声

-
{year}
+
{year === 0 ? 'ALL' : year}
密语-CipherTalk
diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index be1de50..db37f58 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -82,6 +82,8 @@ function SettingsPage() { const [wxidOptions, setWxidOptions] = useState([]) const [showWxidDropdown, setShowWxidDropdown] = useState(false) const [isScanningWxid, setIsScanningWxid] = useState(false) + const [isAccountVerified, setIsAccountVerified] = useState(false) + const [isVerifyingAccount, setIsVerifyingAccount] = useState(false) const [cachePath, setCachePath] = useState('') const [imageXorKey, setImageXorKey] = useState('') const [imageAesKey, setImageAesKey] = useState('') @@ -636,21 +638,21 @@ function SettingsPage() { setIsScanningWxid(true) try { const wxids = await window.electronAPI.dbPath.scanWxids(dbPath) + setIsAccountVerified(false) if (wxids.length === 0) { showMessage('未检测到账号目录(需包含 db_storage 文件夹)', false) setWxidOptions([]) } else if (wxids.length === 1) { // 只有一个账号,直接设置 setWxid(wxids[0]) - await configService.setMyWxid(wxids[0]) - showMessage(`已检测到账号:${wxids[0]}`, true) + showMessage(`已检测到候选账号目录:${wxids[0]}(待验证)`, true) setWxidOptions([]) setShowWxidDropdown(false) } else { // 多个账号,显示选择下拉框 setWxidOptions(wxids) setShowWxidDropdown(true) - showMessage(`检测到 ${wxids.length} 个账号,请选择`, true) + showMessage(`检测到 ${wxids.length} 个候选账号目录,请选择后验证`, true) } } catch (e) { showMessage(`扫描失败: ${e}`, false) @@ -662,16 +664,41 @@ function SettingsPage() { // 选择 wxid const handleSelectWxid = async (selectedWxid: string) => { setWxid(selectedWxid) - await configService.setMyWxid(selectedWxid) + setIsAccountVerified(false) setShowWxidDropdown(false) - showMessage(`已选择账号:${selectedWxid}`, true) + showMessage(`已选择候选账号目录:${selectedWxid}(待验证)`, true) + } + + const handleVerifyAccountDirectory = async () => { + if (!dbPath) { showMessage('请先选择数据库目录', false); return } + if (!decryptKey || decryptKey.length !== 64) { showMessage('请先配置64位解密密钥', false); return } + if (!wxid) { showMessage('请先选择账号目录', false); return } + + setIsVerifyingAccount(true) + try { + const result = await window.electronAPI.wcdb.testConnection(dbPath, decryptKey, wxid) + if (result.success) { + setIsAccountVerified(true) + await configService.setMyWxid(wxid) + showMessage(`账号目录验证成功:${wxid}`, true) + } else { + setIsAccountVerified(false) + showMessage(result.error || '账号目录验证失败,请更换目录重试', false) + } + } catch (e) { + setIsAccountVerified(false) + showMessage(`账号目录验证失败: ${e}`, false) + } finally { + setIsVerifyingAccount(false) + } } const handleTestConnection = async () => { if (!dbPath) { showMessage('请先选择数据库目录', false); return } if (!decryptKey) { showMessage('请先输入解密密钥', false); return } if (decryptKey.length !== 64) { showMessage('密钥长度必须为64个字符', false); return } - if (!wxid) { showMessage('请先输入或扫描 wxid', false); return } + if (!wxid) { showMessage('请先选择账号目录', false); return } + if (!isAccountVerified) { showMessage('请先验证账号目录', false); return } setIsTesting(true) try { @@ -734,7 +761,7 @@ function SettingsPage() { await configService.setAiMessageLimit(aiMessageLimit) // 如果数据库配置完整,尝试设置已连接状态(不进行耗时测试,仅标记) - if (decryptKey && dbPath && wxid && decryptKey.length === 64) { + if (decryptKey && dbPath && wxid && decryptKey.length === 64 && isAccountVerified) { setDbConnected(true, dbPath) } @@ -1056,19 +1083,26 @@ function SettingsPage() {
- - 微信账号标识(只包含 db_storage 子目录的文件夹会被识别) + + 先扫描候选目录,获取密钥后再进行验证 setWxid(e.target.value)} + onChange={(e) => { + setWxid(e.target.value) + setIsAccountVerified(false) + }} />
+
+ 状态:{isAccountVerified ? '✅ 已验证' : '⚠️ 未验证'} {/* 多账号选择列表 */} {showWxidDropdown && wxidOptions.length > 1 && ( diff --git a/src/pages/WelcomePage.tsx b/src/pages/WelcomePage.tsx index 6754d54..5b20995 100644 --- a/src/pages/WelcomePage.tsx +++ b/src/pages/WelcomePage.tsx @@ -39,6 +39,8 @@ function WelcomePage({ standalone = false }: WelcomePageProps) { const [cachePath, setCachePath] = useState('') const [wxid, setWxid] = useState('') const [wxidOptions, setWxidOptions] = useState([]) + const [isAccountVerified, setIsAccountVerified] = useState(false) + const [isVerifyingAccount, setIsVerifyingAccount] = useState(false) const [error, setError] = useState('') const [isScanningWxid, setIsScanningWxid] = useState(false) @@ -134,10 +136,38 @@ function WelcomePage({ standalone = false }: WelcomePageProps) { useEffect(() => { setWxidOptions([]) + setIsAccountVerified(false) // 注意:不要清空 wxid,因为它可能是从缓存加载的 // setWxid('') }, [dbPath]) + const verifyAccountDirectory = async (candidateWxid: string, key: string, silent = false) => { + if (!dbPath || !candidateWxid || key.length !== 64) { + setIsAccountVerified(false) + return false + } + + setIsVerifyingAccount(true) + try { + const result = await window.electronAPI.wcdb.testConnection(dbPath, key, candidateWxid) + if (result.success) { + setIsAccountVerified(true) + if (!silent) setDbKeyStatus(`账号目录验证成功:${candidateWxid}`) + return true + } + + setIsAccountVerified(false) + if (!silent) setError(result.error || '账号目录验证失败,请重新选择') + return false + } catch (e) { + setIsAccountVerified(false) + if (!silent) setError(`账号目录验证失败: ${e}`) + return false + } finally { + setIsVerifyingAccount(false) + } + } + // 保存配置到缓存 useEffect(() => { const config = { @@ -223,8 +253,9 @@ function WelcomePage({ standalone = false }: WelcomePageProps) { try { const wxids = await window.electronAPI.dbPath.scanWxids(dbPath) setWxidOptions(wxids) + setIsAccountVerified(false) if (wxids.length > 0) { - // 优先选择以 wxid_ 开头的账号 + // 密钥前仅做候选识别,默认优先 wxid_ 前缀目录 const wxidAccount = wxids.find(id => id.startsWith('wxid_')) const selectedWxid = wxidAccount || wxids[0] setWxid(selectedWxid) @@ -250,16 +281,37 @@ function WelcomePage({ standalone = false }: WelcomePageProps) { const result = await window.electronAPI.wxKey.startGetKey(wechatPath) if (result.success && result.key) { setDecryptKey(result.key) - setDbKeyStatus('密钥获取成功,正在识别账号...') + setDbKeyStatus('密钥获取成功,正在验证账号目录...') setError('') setShowWechatPathPrompt(false) + + // 先尝试当前登录账号检测(强信号) + let accountInfo: { wxid: string; dbPath: string } | null = null + if (dbPath) { + accountInfo = await window.electronAPI.wxKey.detectCurrentAccount(dbPath, 10) + if (!accountInfo) { + accountInfo = await window.electronAPI.wxKey.detectCurrentAccount(dbPath, 60) + } + } + + if (accountInfo) { + setWxid(accountInfo.wxid) + const ok = await verifyAccountDirectory(accountInfo.wxid, result.key, true) + if (ok) { + setDbKeyStatus(`密钥获取成功,已验证账号目录: ${accountInfo.wxid}`) + return + } + } + const wxids = await handleScanWxid(true) if (wxids.length > 1) { - setDbKeyStatus(`密钥获取成功,识别到 ${wxids.length} 个账号,请选择`) + // 多账号时仅作为候选,等待用户选择后再验证 + setDbKeyStatus(`密钥获取成功,识别到 ${wxids.length} 个候选账号目录,请选择后验证`) } else if (wxids.length === 1) { - setDbKeyStatus('密钥获取成功,已自动识别账号') + const ok = await verifyAccountDirectory(wxids[0], result.key, true) + setDbKeyStatus(ok ? '密钥获取成功,已自动识别并验证账号目录' : '密钥获取成功,请手动确认账号目录') } else { - setDbKeyStatus('密钥获取成功') + setDbKeyStatus('密钥获取成功,请手动选择并验证账号目录') } } else { if (result.needManualPath) { @@ -352,8 +404,7 @@ function WelcomePage({ standalone = false }: WelcomePageProps) { if (currentStep.id === 'intro') return true if (currentStep.id === 'db') return Boolean(dbPath) if (currentStep.id === 'cache') return Boolean(cachePath) - if (currentStep.id === 'key') return decryptKey.length === 64 && Boolean(wxid) - if (currentStep.id === 'key') return decryptKey.length === 64 && Boolean(wxid) + if (currentStep.id === 'key') return decryptKey.length === 64 && Boolean(wxid) && isAccountVerified if (currentStep.id === 'image') return true if (currentStep.id === 'security') return true if (currentStep.id === 'decrypt') return false // 最后一步,不能下一步 @@ -366,7 +417,8 @@ function WelcomePage({ standalone = false }: WelcomePageProps) { if (currentStep.id === 'cache' && !cachePath) setError('请填写缓存目录') if (currentStep.id === 'key') { if (decryptKey.length !== 64) setError('密钥长度必须为 64 个字符') - else if (!wxid) setError('未能自动识别 wxid,请尝试重新获取或检查目录') + else if (!wxid) setError('请先选择账号目录') + else if (!isAccountVerified) setError('账号目录尚未验证,请先验证后继续') } return } @@ -381,7 +433,8 @@ function WelcomePage({ standalone = false }: WelcomePageProps) { const handleStartDecrypt = async () => { if (!dbPath) { setError('请先选择数据库目录'); return } - if (!wxid) { setError('请填写微信ID'); return } + if (!wxid) { setError('请先选择账号目录'); return } + if (!isAccountVerified) { setError('账号目录尚未验证,请先验证'); return } if (!decryptKey || decryptKey.length !== 64) { setError('请填写 64 位解密密钥'); return } setIsDecrypting(true) @@ -770,13 +823,16 @@ function WelcomePage({ standalone = false }: WelcomePageProps) { {currentStep.id === 'key' && (
- + setWxid(e.target.value)} + onChange={(e) => { + setWxid(e.target.value) + setIsAccountVerified(false) + }} /> {wxidOptions.length > 0 && (
@@ -784,13 +840,31 @@ function WelcomePage({ standalone = false }: WelcomePageProps) { ))}
)} +
+ +
+
+ 状态:{isAccountVerified ? '✅ 已验证' : '⚠️ 未验证(密钥前只能识别候选目录)'} +
{dbKeyStatus}
} -
获取密钥会自动启动微信并识别账号
+
获取密钥会自动启动微信并识别候选账号目录
点击自动获取后等待提示hook安装成功,然后登录微信即可
)} @@ -938,8 +1012,8 @@ function WelcomePage({ standalone = false }: WelcomePageProps) { {cachePath || '未设置'}
- 微信账号: - {wxid || '未设置'} + 账号目录: + {wxid ? `${wxid}${isAccountVerified ? '(已验证)' : '(未验证)'}` : '未设置'}
解密密钥: