mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-18 12:07:01 +08:00
feat: 完善多账号连接诊断与聊天媒体导出
- 新增微信账号发现、环境诊断和分步数据库连接引导 - 支持按账号安全保存数据库密钥及快速切换账号 - 完善 WCDB 历史消息分片读取和分页状态提示 - 支持导出图片、视频和语音,提供原图优先及缩略图回退 - 更新安装指引、兼容版本说明和相关自动化测试
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import { execFile } from 'child_process'
|
||||
import fs from 'fs-extra'
|
||||
import os from 'os'
|
||||
import path from 'path'
|
||||
import { promisify } from 'util'
|
||||
import { isUsableDbRoot } from './settings-store'
|
||||
|
||||
const execFileAsync = promisify(execFile)
|
||||
|
||||
const platformLabel = (): string => {
|
||||
if (process.platform === 'win32') return `Windows ${os.release()} (${process.arch})`
|
||||
if (process.platform === 'darwin') return `macOS ${os.release()} (${process.arch})`
|
||||
return `${process.platform} ${os.release()} (${process.arch})`
|
||||
}
|
||||
|
||||
async function detectWindowsWechatVersion(): Promise<string> {
|
||||
const script = [
|
||||
'$process = Get-Process Weixin,WeChat -ErrorAction SilentlyContinue | Where-Object Path | Select-Object -First 1',
|
||||
'$candidate = if ($process) { $process.Path } else {',
|
||||
" @($env:ProgramFiles, ${env:ProgramFiles(x86)}) | Where-Object { $_ } | ForEach-Object { Join-Path $_ 'Tencent\\WeChat\\WeChat.exe' } | Where-Object { Test-Path $_ } | Select-Object -First 1",
|
||||
'}',
|
||||
'if ($candidate) { (Get-Item -LiteralPath $candidate).VersionInfo.ProductVersion }'
|
||||
].join('; ')
|
||||
try {
|
||||
const { stdout } = await execFileAsync(
|
||||
'powershell.exe',
|
||||
['-NoProfile', '-NonInteractive', '-Command', script],
|
||||
{ timeout: 3000, windowsHide: true }
|
||||
)
|
||||
return stdout.trim() || '未检测到'
|
||||
} catch {
|
||||
return '未检测到'
|
||||
}
|
||||
}
|
||||
|
||||
async function detectMacWechatVersion(): Promise<string> {
|
||||
const candidates = [
|
||||
'/Applications/WeChat.app/Contents/Info',
|
||||
path.join(os.homedir(), 'Applications/WeChat.app/Contents/Info')
|
||||
]
|
||||
for (const candidate of candidates) {
|
||||
if (!fs.existsSync(`${candidate}.plist`)) continue
|
||||
try {
|
||||
const { stdout } = await execFileAsync(
|
||||
'/usr/bin/defaults',
|
||||
['read', candidate, 'CFBundleShortVersionString'],
|
||||
{ timeout: 3000 }
|
||||
)
|
||||
if (stdout.trim()) return stdout.trim()
|
||||
} catch {
|
||||
// Continue to the next known installation location.
|
||||
}
|
||||
}
|
||||
return '未检测到'
|
||||
}
|
||||
|
||||
export async function detectWechatVersion(): Promise<string> {
|
||||
if (process.platform === 'win32') return detectWindowsWechatVersion()
|
||||
if (process.platform === 'darwin') return detectMacWechatVersion()
|
||||
return '未检测到'
|
||||
}
|
||||
|
||||
export function detectDataStructureVersion(dbRoot: string): string {
|
||||
return isUsableDbRoot(dbRoot) ? '微信 4.x(WCDB)' : '未检测到'
|
||||
}
|
||||
|
||||
export function getOsVersionLabel(): string {
|
||||
return platformLabel()
|
||||
}
|
||||
@@ -86,7 +86,7 @@ function unique(values: string[]): string[] {
|
||||
return Array.from(new Set(values))
|
||||
}
|
||||
|
||||
function isUsableDbRoot(candidate?: string): boolean {
|
||||
export function isUsableDbRoot(candidate?: string): boolean {
|
||||
if (!candidate || !fs.existsSync(candidate)) return false
|
||||
if (fs.existsSync(path.join(candidate, 'db_storage'))) return true
|
||||
try {
|
||||
@@ -98,6 +98,21 @@ function isUsableDbRoot(candidate?: string): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
export function validateDbRoot(candidate?: string): { valid: boolean; error?: string } {
|
||||
const root = String(candidate || '').trim()
|
||||
if (!root) return { valid: false, error: '微信数据目录为空,请重新选择目录' }
|
||||
if (!fs.existsSync(root)) {
|
||||
return { valid: false, error: '微信数据目录不存在,请检查路径或重新选择目录' }
|
||||
}
|
||||
if (!isUsableDbRoot(root)) {
|
||||
return {
|
||||
valid: false,
|
||||
error: '所选目录中未找到微信 4.x 数据库(db_storage),请选择 xwechat_files 或账号目录'
|
||||
}
|
||||
}
|
||||
return { valid: true }
|
||||
}
|
||||
|
||||
const defaultDbRoot = getDefaultDbRoot()
|
||||
|
||||
const DEFAULT_SETTINGS: AppSettings = {
|
||||
|
||||
Reference in New Issue
Block a user