feat: 完善多账号连接诊断与聊天媒体导出

- 新增微信账号发现、环境诊断和分步数据库连接引导
- 支持按账号安全保存数据库密钥及快速切换账号
- 完善 WCDB 历史消息分片读取和分页状态提示
- 支持导出图片、视频和语音,提供原图优先及缩略图回退
- 更新安装指引、兼容版本说明和相关自动化测试
This commit is contained in:
Wxw-Gu
2026-08-03 10:43:14 +08:00
parent 224308f0e0
commit 08e1294e5d
37 changed files with 2011 additions and 255 deletions
+58
View File
@@ -0,0 +1,58 @@
import crypto from 'crypto'
import fs from 'fs-extra'
import path from 'path'
import type { AccountDiscoveryResult, WechatAccountCandidate } from '../../shared/database-key'
import { DatabaseKeyStore } from '../database-key-store'
import { getBootstrapCache } from './bootstrap-cache'
import { validateDbRoot } from './settings-store'
function accountId(accountRoot: string): string {
return crypto.createHash('sha256').update(path.resolve(accountRoot).toLowerCase()).digest('hex')
}
export async function discoverAccounts(
inputPath: string,
keyStore: DatabaseKeyStore,
currentAccountRoot?: string
): Promise<AccountDiscoveryResult> {
const validation = validateDbRoot(inputPath)
if (!validation.valid) return { success: false, accounts: [], error: validation.error }
const normalizedInput = path.resolve(inputPath)
const isAccount = await fs.pathExists(path.join(normalizedInput, 'db_storage'))
const roots = isAccount
? [normalizedInput]
: (await fs.readdir(normalizedInput, { withFileTypes: true }))
.filter((entry) => entry.isDirectory())
.map((entry) => path.join(normalizedInput, entry.name))
.filter((candidate) => fs.existsSync(path.join(candidate, 'db_storage')))
const accounts: WechatAccountCandidate[] = await Promise.all(
roots.map(async (accountRoot) => {
const cached = getBootstrapCache(accountRoot)?.self
return {
id: accountId(accountRoot),
accountRoot,
directoryName: path.basename(accountRoot),
wxid: cached?.wxid,
nickname: cached?.nickname,
avatar: cached?.avatar,
hasSavedDbKey: (await keyStore.getStatus(accountRoot)).saved,
loginStatus: currentAccountRoot
? path.resolve(currentAccountRoot).toLowerCase() ===
path.resolve(accountRoot).toLowerCase()
? 'current'
: 'other'
: 'unknown',
selectedByInput: isAccount
}
})
)
return {
success: true,
inputKind: isAccount ? 'account' : 'root',
accounts,
preselectedAccountId: isAccount ? accounts[0]?.id : undefined
}
}