From 4d647a94670a8f5b1d6323b08da26aa5ddce0a32 Mon Sep 17 00:00:00 2001 From: xuncha <1658671838@qq.com> Date: Sat, 24 Jan 2026 12:39:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=96=B0=E5=A2=9E=E4=BA=86=E5=88=87?= =?UTF-8?q?=E6=8D=A2=E8=B4=A6=E5=8F=B7=E7=9A=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- electron/services/config.ts | 2 + package.json | 2 +- src/App.tsx | 8 +- src/pages/AnalyticsPage.tsx | 16 ++- src/pages/ChatPage.tsx | 40 ++++++ src/pages/DataManagementPage.tsx | 5 + src/pages/ExportPage.tsx | 13 ++ src/pages/GroupAnalyticsPage.tsx | 24 +++- src/pages/SettingsPage.scss | 1 - src/pages/SettingsPage.tsx | 203 +++++++++++++++++++++---------- src/pages/SnsPage.tsx | 21 +++- src/pages/WelcomePage.tsx | 17 ++- src/services/config.ts | 34 ++++++ 13 files changed, 300 insertions(+), 86 deletions(-) diff --git a/electron/services/config.ts b/electron/services/config.ts index c6233ef..3ba3a14 100644 --- a/electron/services/config.ts +++ b/electron/services/config.ts @@ -8,6 +8,7 @@ interface ConfigSchema { onboardingDone: boolean imageXorKey: number imageAesKey: string + wxidConfigs: Record // 缓存相关 cachePath: string @@ -40,6 +41,7 @@ export class ConfigService { onboardingDone: false, imageXorKey: 0, imageAesKey: '', + wxidConfigs: {}, cachePath: '', lastOpenedDb: '', lastSession: '', diff --git a/package.json b/package.json index ee97a92..8b678e7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "weflow", - "version": "1.3.1", + "version": "1.4.0", "description": "WeFlow", "main": "dist-electron/main.js", "author": "cc", diff --git a/src/App.tsx b/src/App.tsx index 5bad3d5..c8c4074 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -185,9 +185,15 @@ function App() { const decryptKey = await configService.getDecryptKey() const wxid = await configService.getMyWxid() const onboardingDone = await configService.getOnboardingDone() + const wxidConfig = wxid ? await configService.getWxidConfig(wxid) : null + const effectiveDecryptKey = wxidConfig?.decryptKey || decryptKey + + if (wxidConfig?.decryptKey && wxidConfig.decryptKey !== decryptKey) { + await configService.setDecryptKey(wxidConfig.decryptKey) + } // 如果配置完整,自动测试连接 - if (dbPath && decryptKey && wxid) { + if (dbPath && effectiveDecryptKey && wxid) { if (!onboardingDone) { await configService.setOnboardingDone(true) } diff --git a/src/pages/AnalyticsPage.tsx b/src/pages/AnalyticsPage.tsx index 2bf1868..6b30cab 100644 --- a/src/pages/AnalyticsPage.tsx +++ b/src/pages/AnalyticsPage.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback } from 'react' import { useLocation } from 'react-router-dom' import { Users, Clock, MessageSquare, Send, Inbox, Calendar, Loader2, RefreshCw, User, Medal } from 'lucide-react' import ReactECharts from 'echarts-for-react' @@ -16,7 +16,7 @@ function AnalyticsPage() { const themeMode = useThemeStore((state) => state.themeMode) const { statistics, rankings, timeDistribution, isLoaded, setStatistics, setRankings, setTimeDistribution, markLoaded } = useAnalyticsStore() - const loadData = async (forceRefresh = false) => { + const loadData = useCallback(async (forceRefresh = false) => { if (isLoaded && !forceRefresh) return setIsLoading(true) setError(null) @@ -55,14 +55,22 @@ function AnalyticsPage() { setIsLoading(false) if (removeListener) removeListener() } - } + }, [isLoaded, markLoaded, setRankings, setStatistics, setTimeDistribution]) const location = useLocation() useEffect(() => { const force = location.state?.forceRefresh === true loadData(force) - }, [location.state]) + }, [location.state, loadData]) + + useEffect(() => { + const handleChange = () => { + loadData(true) + } + window.addEventListener('wxid-changed', handleChange as EventListener) + return () => window.removeEventListener('wxid-changed', handleChange as EventListener) + }, [loadData]) const handleRefresh = () => loadData(true) diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx index c5a4467..9142d7b 100644 --- a/src/pages/ChatPage.tsx +++ b/src/pages/ChatPage.tsx @@ -245,6 +245,38 @@ function ChatPage(_props: ChatPageProps) { } }, [loadMyAvatar]) + const handleAccountChanged = useCallback(async () => { + senderAvatarCache.clear() + senderAvatarLoading.clear() + preloadImageKeysRef.current.clear() + lastPreloadSessionRef.current = null + setSessionDetail(null) + setCurrentSession(null) + setSessions([]) + setFilteredSessions([]) + setMessages([]) + setSearchKeyword('') + setConnectionError(null) + setConnected(false) + setConnecting(false) + setHasMoreMessages(true) + setHasMoreLater(false) + await connect() + }, [ + connect, + setConnected, + setConnecting, + setConnectionError, + setCurrentSession, + setFilteredSessions, + setHasMoreLater, + setHasMoreMessages, + setMessages, + setSearchKeyword, + setSessionDetail, + setSessions + ]) + // 加载会话列表(优化:先返回基础数据,异步加载联系人信息) const loadSessions = async (options?: { silent?: boolean }) => { if (options?.silent) { @@ -842,6 +874,14 @@ function ChatPage(_props: ChatPageProps) { } }, []) + useEffect(() => { + const handleChange = () => { + void handleAccountChanged() + } + window.addEventListener('wxid-changed', handleChange as EventListener) + return () => window.removeEventListener('wxid-changed', handleChange as EventListener) + }, [handleAccountChanged]) + useEffect(() => { const nextSet = new Set() for (const msg of messages) { diff --git a/src/pages/DataManagementPage.tsx b/src/pages/DataManagementPage.tsx index 86357bb..2afb507 100644 --- a/src/pages/DataManagementPage.tsx +++ b/src/pages/DataManagementPage.tsx @@ -16,6 +16,11 @@ function DataManagementPage() { setWxid(id) } loadConfig() + const handleChange = () => { + loadConfig() + } + window.addEventListener('wxid-changed', handleChange as EventListener) + return () => window.removeEventListener('wxid-changed', handleChange as EventListener) }, []) return ( diff --git a/src/pages/ExportPage.tsx b/src/pages/ExportPage.tsx index 9783061..11ee295 100644 --- a/src/pages/ExportPage.tsx +++ b/src/pages/ExportPage.tsx @@ -157,6 +157,19 @@ function ExportPage() { loadExportDefaults() }, [loadSessions, loadExportPath, loadExportDefaults]) + useEffect(() => { + const handleChange = () => { + setSelectedSessions(new Set()) + setSearchKeyword('') + setExportResult(null) + setSessions([]) + setFilteredSessions([]) + loadSessions() + } + window.addEventListener('wxid-changed', handleChange as EventListener) + return () => window.removeEventListener('wxid-changed', handleChange as EventListener) + }, [loadSessions]) + useEffect(() => { const removeListener = window.electronAPI.export.onProgress?.((payload) => { setExportProgress({ diff --git a/src/pages/GroupAnalyticsPage.tsx b/src/pages/GroupAnalyticsPage.tsx index cd4d973..3cb07c1 100644 --- a/src/pages/GroupAnalyticsPage.tsx +++ b/src/pages/GroupAnalyticsPage.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef } from 'react' +import { useState, useEffect, useRef, useCallback } from 'react' import { Users, BarChart3, Clock, Image, Loader2, RefreshCw, User, Medal, Search, X, ChevronLeft, Copy, Check } from 'lucide-react' import { Avatar } from '../components/Avatar' import ReactECharts from 'echarts-for-react' @@ -56,7 +56,7 @@ function GroupAnalyticsPage() { useEffect(() => { loadGroups() - }, []) + }, [loadGroups]) useEffect(() => { if (searchQuery) { @@ -93,7 +93,7 @@ function GroupAnalyticsPage() { } }, [dateRangeReady]) - const loadGroups = async () => { + const loadGroups = useCallback(async () => { setIsLoading(true) try { const result = await window.electronAPI.groupAnalytics.getGroupChats() @@ -106,7 +106,23 @@ function GroupAnalyticsPage() { } finally { setIsLoading(false) } - } + }, []) + + useEffect(() => { + const handleChange = () => { + setGroups([]) + setFilteredGroups([]) + setSelectedGroup(null) + setSelectedFunction(null) + setMembers([]) + setRankings([]) + setActiveHours({}) + setMediaStats(null) + void loadGroups() + } + window.addEventListener('wxid-changed', handleChange as EventListener) + return () => window.removeEventListener('wxid-changed', handleChange as EventListener) + }, [loadGroups]) const handleGroupSelect = (group: GroupChatInfo) => { if (selectedGroup?.username !== group.username) { diff --git a/src/pages/SettingsPage.scss b/src/pages/SettingsPage.scss index e5a4bed..89f68e2 100644 --- a/src/pages/SettingsPage.scss +++ b/src/pages/SettingsPage.scss @@ -1156,7 +1156,6 @@ input { flex: 1; - padding-right: 36px; } } diff --git a/src/pages/SettingsPage.tsx b/src/pages/SettingsPage.tsx index 999dc5f..0037a5f 100644 --- a/src/pages/SettingsPage.tsx +++ b/src/pages/SettingsPage.tsx @@ -1,5 +1,6 @@ import { useState, useEffect, useRef } from 'react' import { useAppStore } from '../stores/appStore' +import { useChatStore } from '../stores/chatStore' import { useThemeStore, themes } from '../stores/themeStore' import { useAnalyticsStore } from '../stores/analyticsStore' import { dialog } from '../services/ipc' @@ -28,7 +29,8 @@ interface WxidOption { } function SettingsPage() { - const { setDbConnected, setLoading, reset } = useAppStore() + const { isDbConnected, setDbConnected, setLoading, reset } = useAppStore() + const resetChatStore = useChatStore((state) => state.reset) const { currentTheme, themeMode, setTheme, setThemeMode } = useThemeStore() const clearAnalyticsStoreCache = useAnalyticsStore((state) => state.clearCache) @@ -40,7 +42,6 @@ function SettingsPage() { const [wxid, setWxid] = useState('') const [wxidOptions, setWxidOptions] = useState([]) const [showWxidSelect, setShowWxidSelect] = useState(false) - const wxidDropdownRef = useRef(null) const [showExportFormatSelect, setShowExportFormatSelect] = useState(false) const [showExportDateRangeSelect, setShowExportDateRangeSelect] = useState(false) const [showExportExcelColumnsSelect, setShowExportExcelColumnsSelect] = useState(false) @@ -92,9 +93,6 @@ function SettingsPage() { useEffect(() => { const handleClickOutside = (e: MouseEvent) => { const target = e.target as Node - if (showWxidSelect && wxidDropdownRef.current && !wxidDropdownRef.current.contains(target)) { - setShowWxidSelect(false) - } if (showExportFormatSelect && exportFormatDropdownRef.current && !exportFormatDropdownRef.current.contains(target)) { setShowExportFormatSelect(false) } @@ -107,7 +105,7 @@ function SettingsPage() { } document.addEventListener('mousedown', handleClickOutside) return () => document.removeEventListener('mousedown', handleClickOutside) - }, [showWxidSelect, showExportFormatSelect, showExportDateRangeSelect, showExportExcelColumnsSelect]) + }, [showExportFormatSelect, showExportDateRangeSelect, showExportExcelColumnsSelect]) useEffect(() => { const removeDb = window.electronAPI.key.onDbKeyStatus((payload) => { @@ -142,14 +140,24 @@ function SettingsPage() { const savedExportDefaultVoiceAsText = await configService.getExportDefaultVoiceAsText() const savedExportDefaultExcelCompactColumns = await configService.getExportDefaultExcelCompactColumns() - if (savedKey) setDecryptKey(savedKey) if (savedPath) setDbPath(savedPath) if (savedWxid) setWxid(savedWxid) if (savedCachePath) setCachePath(savedCachePath) - if (savedImageXorKey != null) { - setImageXorKey(`0x${savedImageXorKey.toString(16).toUpperCase().padStart(2, '0')}`) + + const wxidConfig = savedWxid ? await configService.getWxidConfig(savedWxid) : null + const decryptKeyToUse = wxidConfig?.decryptKey ?? savedKey ?? '' + const imageXorKeyToUse = typeof wxidConfig?.imageXorKey === 'number' + ? wxidConfig.imageXorKey + : savedImageXorKey + const imageAesKeyToUse = wxidConfig?.imageAesKey ?? savedImageAesKey ?? '' + + setDecryptKey(decryptKeyToUse) + if (typeof imageXorKeyToUse === 'number') { + setImageXorKey(`0x${imageXorKeyToUse.toString(16).toUpperCase().padStart(2, '0')}`) + } else { + setImageXorKey('') } - if (savedImageAesKey) setImageAesKey(savedImageAesKey) + setImageAesKey(imageAesKeyToUse) setLogEnabled(savedLogEnabled) setAutoTranscribeVoice(savedAutoTranscribe) setTranscribeLanguages(savedTranscribeLanguages) @@ -254,6 +262,103 @@ function SettingsPage() { setTimeout(() => setMessage(null), 3000) } + type WxidKeys = { + decryptKey: string + imageXorKey: number | null + imageAesKey: string + } + + const formatImageXorKey = (value: number) => `0x${value.toString(16).toUpperCase().padStart(2, '0')}` + + const parseImageXorKey = (value: string) => { + if (!value) return null + const parsed = parseInt(value.replace(/^0x/i, ''), 16) + return Number.isNaN(parsed) ? null : parsed + } + + const buildKeysFromState = (): WxidKeys => ({ + decryptKey: decryptKey || '', + imageXorKey: parseImageXorKey(imageXorKey), + imageAesKey: imageAesKey || '' + }) + + const buildKeysFromConfig = (wxidConfig: configService.WxidConfig | null): WxidKeys => ({ + decryptKey: wxidConfig?.decryptKey || '', + imageXorKey: typeof wxidConfig?.imageXorKey === 'number' ? wxidConfig.imageXorKey : null, + imageAesKey: wxidConfig?.imageAesKey || '' + }) + + const applyKeysToState = (keys: WxidKeys) => { + setDecryptKey(keys.decryptKey) + if (typeof keys.imageXorKey === 'number') { + setImageXorKey(formatImageXorKey(keys.imageXorKey)) + } else { + setImageXorKey('') + } + setImageAesKey(keys.imageAesKey) + } + + const syncKeysToConfig = async (keys: WxidKeys) => { + await configService.setDecryptKey(keys.decryptKey) + await configService.setImageXorKey(typeof keys.imageXorKey === 'number' ? keys.imageXorKey : 0) + await configService.setImageAesKey(keys.imageAesKey) + } + + const applyWxidSelection = async ( + selectedWxid: string, + options?: { preferCurrentKeys?: boolean; showToast?: boolean; toastText?: string } + ) => { + if (!selectedWxid) return + + const currentWxid = wxid + const isSameWxid = currentWxid === selectedWxid + if (currentWxid && currentWxid !== selectedWxid) { + const currentKeys = buildKeysFromState() + await configService.setWxidConfig(currentWxid, { + decryptKey: currentKeys.decryptKey, + imageXorKey: typeof currentKeys.imageXorKey === 'number' ? currentKeys.imageXorKey : 0, + imageAesKey: currentKeys.imageAesKey + }) + } + + const preferCurrentKeys = options?.preferCurrentKeys ?? false + const keys = preferCurrentKeys + ? buildKeysFromState() + : buildKeysFromConfig(await configService.getWxidConfig(selectedWxid)) + + setWxid(selectedWxid) + applyKeysToState(keys) + await configService.setMyWxid(selectedWxid) + await syncKeysToConfig(keys) + await configService.setWxidConfig(selectedWxid, { + decryptKey: keys.decryptKey, + imageXorKey: typeof keys.imageXorKey === 'number' ? keys.imageXorKey : 0, + imageAesKey: keys.imageAesKey + }) + setShowWxidSelect(false) + if (isDbConnected) { + try { + await window.electronAPI.chat.close() + const result = await window.electronAPI.chat.connect() + setDbConnected(result.success, dbPath || undefined) + if (!result.success && result.error) { + showMessage(result.error, false) + } + } catch (e) { + showMessage(`切换账号后重新连接失败: ${e}`, false) + setDbConnected(false) + } + } + if (!isSameWxid) { + clearAnalyticsStoreCache() + resetChatStore() + window.dispatchEvent(new CustomEvent('wxid-changed', { detail: { wxid: selectedWxid } })) + } + if (options?.showToast ?? true) { + showMessage(options?.toastText || `已选择账号:${selectedWxid}`, true) + } + } + const handleAutoDetectPath = async () => { if (isDetectingPath) return setIsDetectingPath(true) @@ -267,11 +372,10 @@ function SettingsPage() { const wxids = await window.electronAPI.dbPath.scanWxids(result.path) setWxidOptions(wxids) if (wxids.length === 1) { - setWxid(wxids[0].wxid) - await configService.setMyWxid(wxids[0].wxid) - showMessage(`已检测到账号:${wxids[0].wxid}`, true) + await applyWxidSelection(wxids[0].wxid, { + toastText: `已检测到账号:${wxids[0].wxid}` + }) } else if (wxids.length > 1) { - // 多账号时弹出选择对话框 setShowWxidSelect(true) } } else { @@ -296,7 +400,10 @@ function SettingsPage() { } } - const handleScanWxid = async (silent = false) => { + const handleScanWxid = async ( + silent = false, + options?: { preferCurrentKeys?: boolean; showDialog?: boolean } + ) => { if (!dbPath) { if (!silent) showMessage('请先选择数据库目录', false) return @@ -304,12 +411,14 @@ function SettingsPage() { try { const wxids = await window.electronAPI.dbPath.scanWxids(dbPath) setWxidOptions(wxids) + const allowDialog = options?.showDialog ?? !silent if (wxids.length === 1) { - setWxid(wxids[0].wxid) - await configService.setMyWxid(wxids[0].wxid) - if (!silent) showMessage(`已检测到账号:${wxids[0].wxid}`, true) - } else if (wxids.length > 1) { - // 多账号时弹出选择对话框 + await applyWxidSelection(wxids[0].wxid, { + preferCurrentKeys: options?.preferCurrentKeys ?? false, + showToast: !silent, + toastText: `已检测到账号:${wxids[0].wxid}` + }) + } else if (wxids.length > 1 && allowDialog) { setShowWxidSelect(true) } else { if (!silent) showMessage('未检测到账号目录,请检查路径', false) @@ -320,10 +429,7 @@ function SettingsPage() { } const handleSelectWxid = async (selectedWxid: string) => { - setWxid(selectedWxid) - await configService.setMyWxid(selectedWxid) - setShowWxidSelect(false) - showMessage(`已选择账号:${selectedWxid}`, true) + await applyWxidSelection(selectedWxid) } const handleSelectCachePath = async () => { @@ -396,7 +502,7 @@ function SettingsPage() { setDecryptKey(result.key) setDbKeyStatus('密钥获取成功') showMessage('已自动获取解密密钥', true) - await handleScanWxid(true) + await handleScanWxid(true, { preferCurrentKeys: true, showDialog: false }) } else { if (result.error?.includes('未找到微信安装路径') || result.error?.includes('启动微信失败')) { setIsManualStartPrompt(true) @@ -482,19 +588,14 @@ function SettingsPage() { await configService.setDbPath(dbPath) await configService.setMyWxid(wxid) await configService.setCachePath(cachePath) - if (imageXorKey) { - const parsed = parseInt(imageXorKey.replace(/^0x/i, ''), 16) - if (!Number.isNaN(parsed)) { - await configService.setImageXorKey(parsed) - } - } else { - await configService.setImageXorKey(0) - } - if (imageAesKey) { - await configService.setImageAesKey(imageAesKey) - } else { - await configService.setImageAesKey('') - } + const parsedXorKey = parseImageXorKey(imageXorKey) + await configService.setImageXorKey(typeof parsedXorKey === 'number' ? parsedXorKey : 0) + await configService.setImageAesKey(imageAesKey || '') + await configService.setWxidConfig(wxid, { + decryptKey, + imageXorKey: typeof parsedXorKey === 'number' ? parsedXorKey : 0, + imageAesKey + }) await configService.setWhisperModelDir(whisperModelDir) await configService.setAutoTranscribeVoice(autoTranscribeVoice) await configService.setTranscribeLanguages(transcribeLanguages) @@ -687,37 +788,13 @@ function SettingsPage() {
微信账号标识 -
+
setWxid(e.target.value)} /> - - {showWxidSelect && wxidOptions.length > 0 && ( -
- {wxidOptions.map((opt) => ( -
handleSelectWxid(opt.wxid)} - > - {opt.wxid} - - {new Date(opt.modifiedTime).toLocaleDateString()} - -
- ))} -
- )}
diff --git a/src/pages/SnsPage.tsx b/src/pages/SnsPage.tsx index 04bf2d1..a0dbb14 100644 --- a/src/pages/SnsPage.tsx +++ b/src/pages/SnsPage.tsx @@ -111,7 +111,7 @@ export default function SnsPage() { }, [offset, selectedUsernames, searchKeyword, startDate, endDate]) // 获取联系人列表 - const loadContacts = async () => { + const loadContacts = useCallback(async () => { setContactsLoading(true) try { const result = await window.electronAPI.chat.getSessions() @@ -171,11 +171,26 @@ export default function SnsPage() { } finally { setContactsLoading(false) } - } + }, []) useEffect(() => { loadContacts() - }, []) + }, [loadContacts]) + + useEffect(() => { + const handleChange = () => { + setPosts([]) + setHasMore(true) + setHasNewer(false) + setSelectedUsernames([]) + setSearchKeyword('') + setJumpTargetDate(null) + loadContacts() + loadPosts({ reset: true }) + } + window.addEventListener('wxid-changed', handleChange as EventListener) + return () => window.removeEventListener('wxid-changed', handleChange as EventListener) + }, [loadContacts, loadPosts]) useEffect(() => { loadPosts(true) diff --git a/src/pages/WelcomePage.tsx b/src/pages/WelcomePage.tsx index 3f5ac52..e885208 100644 --- a/src/pages/WelcomePage.tsx +++ b/src/pages/WelcomePage.tsx @@ -269,15 +269,14 @@ function WelcomePage({ standalone = false }: WelcomePageProps) { await configService.setDecryptKey(decryptKey) await configService.setMyWxid(wxid) await configService.setCachePath(cachePath) - if (imageXorKey) { - const parsed = parseInt(imageXorKey.replace(/^0x/i, ''), 16) - if (!Number.isNaN(parsed)) { - await configService.setImageXorKey(parsed) - } - } - if (imageAesKey) { - await configService.setImageAesKey(imageAesKey) - } + const parsedXorKey = imageXorKey ? parseInt(imageXorKey.replace(/^0x/i, ''), 16) : null + await configService.setImageXorKey(typeof parsedXorKey === 'number' && !Number.isNaN(parsedXorKey) ? parsedXorKey : 0) + await configService.setImageAesKey(imageAesKey || '') + await configService.setWxidConfig(wxid, { + decryptKey, + imageXorKey: typeof parsedXorKey === 'number' && !Number.isNaN(parsedXorKey) ? parsedXorKey : 0, + imageAesKey + }) await configService.setOnboardingDone(true) setDbConnected(true, dbPath) diff --git a/src/services/config.ts b/src/services/config.ts index 9bc8a5e..13b7583 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -6,6 +6,7 @@ export const CONFIG_KEYS = { DECRYPT_KEY: 'decryptKey', DB_PATH: 'dbPath', MY_WXID: 'myWxid', + WXID_CONFIGS: 'wxidConfigs', THEME: 'theme', THEME_ID: 'themeId', LAST_SESSION: 'lastSession', @@ -30,6 +31,13 @@ export const CONFIG_KEYS = { EXPORT_DEFAULT_EXCEL_COMPACT_COLUMNS: 'exportDefaultExcelCompactColumns' } as const +export interface WxidConfig { + decryptKey?: string + imageXorKey?: number + imageAesKey?: string + updatedAt?: number +} + // 获取解密密钥 export async function getDecryptKey(): Promise { const value = await config.get(CONFIG_KEYS.DECRYPT_KEY) @@ -63,6 +71,32 @@ export async function setMyWxid(wxid: string): Promise { await config.set(CONFIG_KEYS.MY_WXID, wxid) } +export async function getWxidConfigs(): Promise> { + const value = await config.get(CONFIG_KEYS.WXID_CONFIGS) + if (value && typeof value === 'object') { + return value as Record + } + return {} +} + +export async function getWxidConfig(wxid: string): Promise { + if (!wxid) return null + const configs = await getWxidConfigs() + return configs[wxid] || null +} + +export async function setWxidConfig(wxid: string, configValue: WxidConfig): Promise { + if (!wxid) return + const configs = await getWxidConfigs() + const previous = configs[wxid] || {} + configs[wxid] = { + ...previous, + ...configValue, + updatedAt: Date.now() + } + await config.set(CONFIG_KEYS.WXID_CONFIGS, configs) +} + // 获取主题 export async function getTheme(): Promise<'light' | 'dark'> { const value = await config.get(CONFIG_KEYS.THEME)