mirror of
https://github.com/LifeArchiveProject/WeChatDataAnalysis.git
synced 2026-02-19 14:20:51 +08:00
feat(detection): 添加当前登录账号检测功能
This commit is contained in:
@@ -39,6 +39,9 @@
|
|||||||
/* 边框颜色 */
|
/* 边框颜色 */
|
||||||
--border-color: #e7e7e7;
|
--border-color: #e7e7e7;
|
||||||
--border-light: #f4f4f4;
|
--border-light: #f4f4f4;
|
||||||
|
|
||||||
|
/* 统一消息圆角(聊天所有消息共用) */
|
||||||
|
--message-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -52,6 +55,31 @@
|
|||||||
|
|
||||||
/* 微信风格组件样式 */
|
/* 微信风格组件样式 */
|
||||||
@layer components {
|
@layer components {
|
||||||
|
/* 聊天气泡尾巴(左右),与高度 42px 居中对齐 */
|
||||||
|
.bubble-tail-l, .bubble-tail-r { position: relative; }
|
||||||
|
.bubble-tail-l::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -4px;
|
||||||
|
top: 12px; /* 36px 中线 18px,减去 6px ≈ 12px */
|
||||||
|
width: 12px; height: 12px;
|
||||||
|
background: #FFFFFF;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
.bubble-tail-r::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: -4px;
|
||||||
|
top: 12px;
|
||||||
|
width: 12px; height: 12px;
|
||||||
|
background: #95EC69;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
/* 统一的消息圆角工具类 */
|
||||||
|
.msg-radius { border-radius: var(--message-radius); }
|
||||||
|
.msg-bubble { @apply leading-normal break-words text-pretty; border-radius: var(--message-radius); }
|
||||||
/* 按钮样式 */
|
/* 按钮样式 */
|
||||||
.btn {
|
.btn {
|
||||||
@apply px-6 py-3 rounded-full font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 transform active:scale-95;
|
@apply px-6 py-3 rounded-full font-medium transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 transform active:scale-95;
|
||||||
|
|||||||
@@ -27,8 +27,23 @@ export const useApi = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 微信检测API
|
// 微信检测API
|
||||||
const detectWechat = async () => {
|
const detectWechat = async (params = {}) => {
|
||||||
return await request('/wechat-detection')
|
const query = new URLSearchParams()
|
||||||
|
if (params && params.data_root_path) {
|
||||||
|
query.set('data_root_path', params.data_root_path)
|
||||||
|
}
|
||||||
|
const url = '/wechat-detection' + (query.toString() ? `?${query.toString()}` : '')
|
||||||
|
return await request(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检测当前登录账号API
|
||||||
|
const detectCurrentAccount = async (params = {}) => {
|
||||||
|
const query = new URLSearchParams()
|
||||||
|
if (params && params.data_root_path) {
|
||||||
|
query.set('data_root_path', params.data_root_path)
|
||||||
|
}
|
||||||
|
const url = '/current-account' + (query.toString() ? `?${query.toString()}` : '')
|
||||||
|
return await request(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 数据库解密API
|
// 数据库解密API
|
||||||
@@ -46,6 +61,7 @@ export const useApi = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
detectWechat,
|
detectWechat,
|
||||||
|
detectCurrentAccount,
|
||||||
decryptDatabase,
|
decryptDatabase,
|
||||||
healthCheck
|
healthCheck
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,7 +91,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="w-12 h-12 bg-[#10AEEF]/10 rounded-lg flex items-center justify-center">
|
<div class="w-12 h-12 bg-[#10AEEF]/10 rounded-lg flex items-center justify-center">
|
||||||
<svg class="w-6 h-6 text-[#10AEEF]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-6 h-6 text-[#10AEEF]" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283-.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,6 +110,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 账户列表 -->
|
<!-- 账户列表 -->
|
||||||
@@ -119,8 +120,9 @@
|
|||||||
<h3 class="text-base font-semibold text-[#000000e6]">微信账户详情</h3>
|
<h3 class="text-base font-semibold text-[#000000e6]">微信账户详情</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="divide-y divide-[#EDEDED] max-h-64 overflow-y-auto">
|
<div class="divide-y divide-[#EDEDED] max-h-64 overflow-y-auto">
|
||||||
<div v-for="(account, index) in detectionResult.data.accounts" :key="index"
|
<!-- 将当前登录账号放在第一位 -->
|
||||||
class="p-4 hover:bg-gray-50 transition-all duration-200">
|
<div v-for="(account, index) in sortedAccounts" :key="index"
|
||||||
|
:class="['p-4 hover:bg-gray-50 transition-all duration-200', isCurrentAccount(account.account_name) ? 'bg-[#07C160]/5' : '']">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
@@ -128,7 +130,17 @@
|
|||||||
<span class="text-[#07C160] font-bold text-lg">{{ account.account_name?.charAt(0)?.toUpperCase() || 'U' }}</span>
|
<span class="text-[#07C160] font-bold text-lg">{{ account.account_name?.charAt(0)?.toUpperCase() || 'U' }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
<div class="flex items-center">
|
||||||
<p class="text-lg font-medium text-[#000000e6]">{{ account.account_name || '未知账户' }}</p>
|
<p class="text-lg font-medium text-[#000000e6]">{{ account.account_name || '未知账户' }}</p>
|
||||||
|
<!-- 当前登录账号标识 -->
|
||||||
|
<span v-if="isCurrentAccount(account.account_name)"
|
||||||
|
class="ml-2 inline-flex items-center px-2 py-1 bg-[#07C160]/10 text-[#07C160] rounded text-xs font-medium">
|
||||||
|
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fill-rule="evenodd" d="M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z" clip-rule="evenodd"/>
|
||||||
|
</svg>
|
||||||
|
当前登录
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<div class="flex items-center mt-1 space-x-4 text-sm text-[#7F7F7F]">
|
<div class="flex items-center mt-1 space-x-4 text-sm text-[#7F7F7F]">
|
||||||
<span class="flex items-center">
|
<span class="flex items-center">
|
||||||
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg class="w-4 h-4 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
@@ -192,14 +204,35 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { useApi } from '~/composables/useApi'
|
import { useApi } from '~/composables/useApi'
|
||||||
|
import { useAppStore } from '~/stores/app'
|
||||||
|
|
||||||
const { detectWechat } = useApi()
|
const { detectWechat, detectCurrentAccount } = useApi()
|
||||||
|
const appStore = useAppStore()
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const detectionResult = ref(null)
|
const detectionResult = ref(null)
|
||||||
const customPath = ref('')
|
const customPath = ref('')
|
||||||
|
|
||||||
|
// 计算属性:将当前登录账号排在第一位
|
||||||
|
const sortedAccounts = computed(() => {
|
||||||
|
if (!detectionResult.value?.data?.accounts) return []
|
||||||
|
|
||||||
|
const accounts = [...detectionResult.value.data.accounts]
|
||||||
|
const currentAccountName = detectionResult.value.data?.current_account?.current_account
|
||||||
|
|
||||||
|
if (!currentAccountName) return accounts
|
||||||
|
|
||||||
|
// 将当前登录账号移到第一位
|
||||||
|
const sorted = accounts.sort((a, b) => {
|
||||||
|
if (a.account_name === currentAccountName) return -1
|
||||||
|
if (b.account_name === currentAccountName) return 1
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
return sorted
|
||||||
|
})
|
||||||
|
|
||||||
// 开始检测
|
// 开始检测
|
||||||
const startDetection = async () => {
|
const startDetection = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -209,8 +242,28 @@ const startDetection = async () => {
|
|||||||
if (customPath.value && customPath.value.trim()) {
|
if (customPath.value && customPath.value.trim()) {
|
||||||
params.data_root_path = customPath.value.trim()
|
params.data_root_path = customPath.value.trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检测微信安装信息
|
||||||
const result = await detectWechat(params)
|
const result = await detectWechat(params)
|
||||||
detectionResult.value = result
|
detectionResult.value = result
|
||||||
|
|
||||||
|
// 如果检测成功,同时检测当前登录账号
|
||||||
|
if (result.status === 'success') {
|
||||||
|
try {
|
||||||
|
const currentAccountResult = await detectCurrentAccount(params)
|
||||||
|
if (currentAccountResult.status === 'success') {
|
||||||
|
// 保存当前账号信息到状态管理
|
||||||
|
appStore.setCurrentAccount(currentAccountResult.data)
|
||||||
|
|
||||||
|
// 同时更新检测结果中的当前账号信息
|
||||||
|
if (detectionResult.value.data) {
|
||||||
|
detectionResult.value.data.current_account = currentAccountResult.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (accountErr) {
|
||||||
|
console.error('检测当前登录账号失败:', accountErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('检测过程中发生错误:', err)
|
console.error('检测过程中发生错误:', err)
|
||||||
detectionResult.value = {
|
detectionResult.value = {
|
||||||
@@ -237,6 +290,36 @@ const goToDecrypt = (account) => {
|
|||||||
navigateTo('/decrypt')
|
navigateTo('/decrypt')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 判断是否为当前登录账号
|
||||||
|
const isCurrentAccount = (accountName) => {
|
||||||
|
if (!detectionResult.value?.data?.current_account) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return detectionResult.value.data.current_account.current_account === accountName
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前登录账号信息
|
||||||
|
const getCurrentAccountInfo = () => {
|
||||||
|
return detectionResult.value?.data?.current_account
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化时间显示
|
||||||
|
const formatTime = (timeString) => {
|
||||||
|
if (!timeString) return ''
|
||||||
|
try {
|
||||||
|
const date = new Date(timeString)
|
||||||
|
return date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
return timeString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 页面加载时自动检测
|
// 页面加载时自动检测
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
startDetection()
|
startDetection()
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ export const useAppStore = defineStore('app', {
|
|||||||
// 最近的检测结果
|
// 最近的检测结果
|
||||||
lastDetectionResult: null,
|
lastDetectionResult: null,
|
||||||
|
|
||||||
|
// 当前登录账号信息
|
||||||
|
currentAccount: null,
|
||||||
|
|
||||||
// 全局加载状态
|
// 全局加载状态
|
||||||
globalLoading: false,
|
globalLoading: false,
|
||||||
|
|
||||||
@@ -28,6 +31,11 @@ export const useAppStore = defineStore('app', {
|
|||||||
this.lastDetectionResult = result
|
this.lastDetectionResult = result
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 设置当前登录账号
|
||||||
|
setCurrentAccount(account) {
|
||||||
|
this.currentAccount = account
|
||||||
|
},
|
||||||
|
|
||||||
// 设置全局加载状态
|
// 设置全局加载状态
|
||||||
setGlobalLoading(loading) {
|
setGlobalLoading(loading) {
|
||||||
this.globalLoading = loading
|
this.globalLoading = loading
|
||||||
|
|||||||
@@ -277,9 +277,13 @@ async def detect_wechat_detailed(data_root_path: Optional[str] = None):
|
|||||||
"""详细检测微信安装信息,包括版本、路径、消息目录等。"""
|
"""详细检测微信安装信息,包括版本、路径、消息目录等。"""
|
||||||
logger.info("开始执行微信检测")
|
logger.info("开始执行微信检测")
|
||||||
try:
|
try:
|
||||||
from .wechat_detection import detect_wechat_installation
|
from .wechat_detection import detect_wechat_installation, detect_current_logged_in_account
|
||||||
info = detect_wechat_installation(data_root_path=data_root_path)
|
info = detect_wechat_installation(data_root_path=data_root_path)
|
||||||
|
|
||||||
|
# 检测当前登录账号
|
||||||
|
current_account_info = detect_current_logged_in_account(data_root_path)
|
||||||
|
info['current_account'] = current_account_info
|
||||||
|
|
||||||
# 添加一些统计信息
|
# 添加一些统计信息
|
||||||
stats = {
|
stats = {
|
||||||
'total_databases': len(info['databases']),
|
'total_databases': len(info['databases']),
|
||||||
@@ -306,6 +310,29 @@ async def detect_wechat_detailed(data_root_path: Optional[str] = None):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/current-account", summary="检测当前登录账号")
|
||||||
|
async def detect_current_account(data_root_path: Optional[str] = None):
|
||||||
|
"""检测当前登录的微信账号"""
|
||||||
|
logger.info("开始检测当前登录账号")
|
||||||
|
try:
|
||||||
|
from .wechat_detection import detect_current_logged_in_account
|
||||||
|
result = detect_current_logged_in_account(data_root_path)
|
||||||
|
|
||||||
|
logger.info(f"当前账号检测完成: {result.get('message', '无结果')}")
|
||||||
|
|
||||||
|
return {
|
||||||
|
'status': 'success',
|
||||||
|
'data': result
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"当前账号检测失败: {str(e)}")
|
||||||
|
return {
|
||||||
|
'status': 'error',
|
||||||
|
'error': str(e),
|
||||||
|
'data': None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import ctypes
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Dict, Any, Union
|
from typing import List, Dict, Any, Union
|
||||||
from ctypes import wintypes
|
from ctypes import wintypes
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -421,6 +422,135 @@ def detect_wechat_accounts_from_backup(backup_base_path: str = None) -> List[Dic
|
|||||||
return accounts
|
return accounts
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_login_paths_from_base(provided_path: str) -> Dict[str, str]:
|
||||||
|
"""
|
||||||
|
根据用户提供的路径推断 base_path 和 login_dir。
|
||||||
|
|
||||||
|
兼容三种传入:
|
||||||
|
- 直接传 xwechat_files 根目录
|
||||||
|
- 传 xwechat_files/all_users
|
||||||
|
- 传 xwechat_files/all_users/login
|
||||||
|
"""
|
||||||
|
base_path = provided_path
|
||||||
|
login_dir = os.path.join(provided_path, "all_users", "login")
|
||||||
|
|
||||||
|
try:
|
||||||
|
norm = os.path.normpath(provided_path)
|
||||||
|
last = os.path.basename(norm).lower()
|
||||||
|
parent = os.path.dirname(norm)
|
||||||
|
parent_last = os.path.basename(parent).lower() if parent else ""
|
||||||
|
|
||||||
|
if last == "login" and parent_last == "all_users":
|
||||||
|
# .../xwechat_files/all_users/login -> base_path 为 xwechat_files
|
||||||
|
base_path = os.path.dirname(parent)
|
||||||
|
login_dir = norm
|
||||||
|
elif last == "all_users":
|
||||||
|
# .../xwechat_files/all_users -> login_dir 追加 login
|
||||||
|
base_path = os.path.dirname(norm)
|
||||||
|
login_dir = os.path.join(norm, "login")
|
||||||
|
else:
|
||||||
|
# 认为传的是 xwechat_files 根
|
||||||
|
base_path = norm
|
||||||
|
login_dir = os.path.join(norm, "all_users", "login")
|
||||||
|
except Exception:
|
||||||
|
# 兜底:保持初始推断
|
||||||
|
pass
|
||||||
|
|
||||||
|
return {"base_path": base_path, "login_dir": login_dir}
|
||||||
|
|
||||||
|
|
||||||
|
def detect_wechat_accounts_from_login(login_base_path: str = None) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
通过登录信息目录检测微信账号,并映射到实际数据目录。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
login_base_path: 可选的微信数据根目录。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
账号信息列表(与 Backup 检测返回结构一致)
|
||||||
|
"""
|
||||||
|
accounts: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
|
# 若用户提供路径,则优先按该路径推断
|
||||||
|
if login_base_path:
|
||||||
|
paths = _resolve_login_paths_from_base(login_base_path)
|
||||||
|
base_path = paths["base_path"]
|
||||||
|
login_dir = paths["login_dir"]
|
||||||
|
|
||||||
|
if not os.path.exists(login_dir):
|
||||||
|
return accounts
|
||||||
|
else:
|
||||||
|
# 自动检测:遍历候选根目录,寻找登录信息目录
|
||||||
|
base_path = None
|
||||||
|
login_dir = None
|
||||||
|
detected_dirs = auto_detect_wechat_data_dirs()
|
||||||
|
for detected_dir in detected_dirs:
|
||||||
|
try:
|
||||||
|
test_login = os.path.join(detected_dir, "all_users", "login")
|
||||||
|
if os.path.exists(test_login):
|
||||||
|
base_path = detected_dir
|
||||||
|
login_dir = test_login
|
||||||
|
break
|
||||||
|
|
||||||
|
# 也检查一层子目录
|
||||||
|
for sub in os.listdir(detected_dir):
|
||||||
|
sub_path = os.path.join(detected_dir, sub)
|
||||||
|
if not os.path.isdir(sub_path):
|
||||||
|
continue
|
||||||
|
test_login = os.path.join(sub_path, "all_users", "login")
|
||||||
|
if os.path.exists(test_login):
|
||||||
|
base_path = sub_path
|
||||||
|
login_dir = test_login
|
||||||
|
break
|
||||||
|
if base_path:
|
||||||
|
break
|
||||||
|
except (PermissionError, OSError):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not base_path or not login_dir:
|
||||||
|
return accounts
|
||||||
|
|
||||||
|
# 枚举 login 目录下的子项,每个子项代表一个账号标识(可能是文件或文件夹)
|
||||||
|
try:
|
||||||
|
for item in os.listdir(login_dir):
|
||||||
|
account_name = item
|
||||||
|
account_login_item_path = os.path.join(login_dir, item)
|
||||||
|
# 无论是文件还是文件夹,都视为一个账号标识
|
||||||
|
if not os.path.exists(account_login_item_path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 在 base_path 下查找以 {account_name}_ 开头的数据目录(与 Backup 规则一致)
|
||||||
|
data_dir = None
|
||||||
|
try:
|
||||||
|
for data_item in os.listdir(base_path):
|
||||||
|
data_item_path = os.path.join(base_path, data_item)
|
||||||
|
if (
|
||||||
|
os.path.isdir(data_item_path)
|
||||||
|
and data_item.startswith(f"{account_name}_")
|
||||||
|
and data_item not in ["Backup", "all_users"]
|
||||||
|
):
|
||||||
|
data_dir = data_item_path
|
||||||
|
break
|
||||||
|
except (PermissionError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
databases = collect_account_databases(data_dir, account_name) if data_dir else []
|
||||||
|
|
||||||
|
accounts.append(
|
||||||
|
{
|
||||||
|
"account_name": account_name,
|
||||||
|
"backup_dir": None,
|
||||||
|
"data_dir": data_dir,
|
||||||
|
"databases": databases,
|
||||||
|
"database_count": len(databases),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
except (PermissionError, OSError):
|
||||||
|
# 无权限访问时返回已收集的账号
|
||||||
|
return accounts
|
||||||
|
|
||||||
|
return accounts
|
||||||
|
|
||||||
def collect_account_databases(data_dir: str, account_name: str) -> List[Dict[str, Any]]:
|
def collect_account_databases(data_dir: str, account_name: str) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
收集指定账号数据目录下的所有数据库文件
|
收集指定账号数据目录下的所有数据库文件
|
||||||
@@ -474,7 +604,7 @@ def collect_account_databases(data_dir: str, account_name: str) -> List[Dict[str
|
|||||||
return databases
|
return databases
|
||||||
|
|
||||||
|
|
||||||
def detect_wechat_installation() -> Dict[str, Any]:
|
def detect_wechat_installation(data_root_path: str | None = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
检测微信安装情况 - 改进的多账户检测逻辑
|
检测微信安装情况 - 改进的多账户检测逻辑
|
||||||
"""
|
"""
|
||||||
@@ -528,47 +658,92 @@ def detect_wechat_installation() -> Dict[str, Any]:
|
|||||||
if not result["is_running"]:
|
if not result["is_running"]:
|
||||||
result["detection_methods"].append("未检测到微信进程")
|
result["detection_methods"].append("未检测到微信进程")
|
||||||
|
|
||||||
# 2. 使用新的账号检测逻辑
|
# 2. 使用新的账号检测逻辑:同时支持 Backup 与登录信息目录,并合并结果
|
||||||
result["detection_methods"].append("多账户检测")
|
result["detection_methods"].append("多账户检测(多来源合并)")
|
||||||
try:
|
try:
|
||||||
# 检测指定路径下的微信账号
|
# 支持前端兜底路径:若提供 data_root_path,则两种方式都以该路径为基准
|
||||||
accounts = detect_wechat_accounts_from_backup()
|
accounts_from_backup = detect_wechat_accounts_from_backup(
|
||||||
|
backup_base_path=data_root_path
|
||||||
|
)
|
||||||
|
accounts_from_login = detect_wechat_accounts_from_login(
|
||||||
|
login_base_path=data_root_path
|
||||||
|
)
|
||||||
|
|
||||||
|
# 合并账号:按 account_name 去重,优先保留信息更完整者
|
||||||
|
account_map: Dict[str, Dict[str, Any]] = {}
|
||||||
|
|
||||||
|
def _merge_account(acc: Dict[str, Any]):
|
||||||
|
name = acc.get("account_name")
|
||||||
|
if not name:
|
||||||
|
return
|
||||||
|
if name not in account_map:
|
||||||
|
account_map[name] = {
|
||||||
|
"account_name": name,
|
||||||
|
"backup_dir": acc.get("backup_dir"),
|
||||||
|
"data_dir": acc.get("data_dir"),
|
||||||
|
"databases": list(acc.get("databases", [])),
|
||||||
|
"database_count": int(acc.get("database_count", 0)),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
existing = account_map[name]
|
||||||
|
if not existing.get("backup_dir") and acc.get("backup_dir"):
|
||||||
|
existing["backup_dir"] = acc.get("backup_dir")
|
||||||
|
if not existing.get("data_dir") and acc.get("data_dir"):
|
||||||
|
existing["data_dir"] = acc.get("data_dir")
|
||||||
|
# 合并数据库(按 path 去重)
|
||||||
|
seen_paths = {d.get("path") for d in existing.get("databases", [])}
|
||||||
|
for db in acc.get("databases", []):
|
||||||
|
if db.get("path") not in seen_paths:
|
||||||
|
existing.setdefault("databases", []).append(db)
|
||||||
|
seen_paths.add(db.get("path"))
|
||||||
|
existing["database_count"] = len(existing.get("databases", []))
|
||||||
|
|
||||||
|
for acc in accounts_from_backup:
|
||||||
|
_merge_account(acc)
|
||||||
|
for acc in accounts_from_login:
|
||||||
|
_merge_account(acc)
|
||||||
|
|
||||||
|
accounts = list(account_map.values())
|
||||||
result["accounts"] = accounts
|
result["accounts"] = accounts
|
||||||
result["total_accounts"] = len(accounts)
|
result["total_accounts"] = len(accounts)
|
||||||
|
|
||||||
# 统计总数据库数量
|
# 统计总数据库数量
|
||||||
total_db_count = sum(account["database_count"] for account in accounts)
|
total_db_count = sum(account.get("database_count", 0) for account in accounts)
|
||||||
result["total_databases"] = total_db_count
|
result["total_databases"] = total_db_count
|
||||||
|
|
||||||
if accounts:
|
if accounts:
|
||||||
result["detection_methods"].append(f"在指定路径检测到 {len(accounts)} 个微信账户")
|
result["detection_methods"].append(
|
||||||
|
f"检测到 {len(accounts)} 个微信账户(已合并两种来源)"
|
||||||
|
)
|
||||||
result["detection_methods"].append(f"总计 {total_db_count} 个数据库文件")
|
result["detection_methods"].append(f"总计 {total_db_count} 个数据库文件")
|
||||||
|
|
||||||
# 为每个账户添加详细信息
|
# 为每个账户添加详细信息
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
account_name = account["account_name"]
|
account_name = account.get("account_name")
|
||||||
db_count = account["database_count"]
|
db_count = account.get("database_count", 0)
|
||||||
data_dir_status = "已找到" if account["data_dir"] else "未找到"
|
data_dir_status = "已找到" if account.get("data_dir") else "未找到"
|
||||||
result["detection_methods"].append(f"账户 {account_name}: {db_count} 个数据库, 数据目录{data_dir_status}")
|
result["detection_methods"].append(
|
||||||
|
f"账户 {account_name}: {db_count} 个数据库, 数据目录{data_dir_status}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
result["detection_methods"].append("未在指定路径检测到微信账户")
|
result["detection_methods"].append("未检测到微信账户")
|
||||||
|
|
||||||
# 填充向后兼容性字段
|
# 填充向后兼容性字段
|
||||||
for account in accounts:
|
for account in accounts:
|
||||||
if account["data_dir"]:
|
if account.get("data_dir"):
|
||||||
result["wechat_data_dirs"].append(account["data_dir"])
|
result["wechat_data_dirs"].append(account["data_dir"])
|
||||||
result["message_dirs"].append(account["data_dir"])
|
result["message_dirs"].append(account["data_dir"])
|
||||||
result["user_accounts"].append(account["account_name"])
|
result["user_accounts"].append(account.get("account_name"))
|
||||||
|
|
||||||
# 添加数据库到兼容性列表
|
# 添加数据库到兼容性列表
|
||||||
for db in account["databases"]:
|
for db in account.get("databases", []):
|
||||||
result["databases"].append({
|
result["databases"].append({
|
||||||
"path": db["path"],
|
"path": db["path"],
|
||||||
"name": db["name"],
|
"name": db["name"],
|
||||||
"type": db["type"],
|
"type": db["type"],
|
||||||
"size": db["size"],
|
"size": db["size"],
|
||||||
"user": account["account_name"],
|
"user": account.get("account_name"),
|
||||||
"user_dir": account["data_dir"]
|
"user_dir": account.get("data_dir"),
|
||||||
})
|
})
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -624,6 +799,129 @@ def detect_wechat_installation() -> Dict[str, Any]:
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def detect_current_logged_in_account(base_path: str = None) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
通过key_info.db文件时间检测当前登录的微信账号
|
||||||
|
|
||||||
|
Args:
|
||||||
|
base_path: 微信数据根目录,如果为None则自动检测
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
当前登录账号信息
|
||||||
|
"""
|
||||||
|
current_account = None
|
||||||
|
latest_time = None
|
||||||
|
|
||||||
|
# 添加调试信息
|
||||||
|
print(f"[DEBUG] 开始检测当前登录账号,提供的base_path: {base_path}")
|
||||||
|
|
||||||
|
# 如果没有指定路径,尝试自动检测
|
||||||
|
if base_path is None:
|
||||||
|
detected_dirs = auto_detect_wechat_data_dirs()
|
||||||
|
print(f"[DEBUG] 自动检测到的目录: {detected_dirs}")
|
||||||
|
if not detected_dirs:
|
||||||
|
return {
|
||||||
|
"current_account": None,
|
||||||
|
"latest_time": None,
|
||||||
|
"message": "未检测到微信数据目录"
|
||||||
|
}
|
||||||
|
base_path = detected_dirs[0]
|
||||||
|
|
||||||
|
print(f"[DEBUG] 使用的base_path: {base_path}")
|
||||||
|
|
||||||
|
# 查找登录信息目录 - 尝试多个可能的路径
|
||||||
|
possible_login_paths = [
|
||||||
|
os.path.join(base_path, "all_users", "login"), # 标准路径
|
||||||
|
os.path.join(base_path, "login"), # 备选路径1
|
||||||
|
]
|
||||||
|
|
||||||
|
# 也尝试在子目录中查找
|
||||||
|
try:
|
||||||
|
for item in os.listdir(base_path):
|
||||||
|
item_path = os.path.join(base_path, item)
|
||||||
|
if os.path.isdir(item_path):
|
||||||
|
possible_login_paths.extend([
|
||||||
|
os.path.join(item_path, "all_users", "login"), # 子目录中的标准路径
|
||||||
|
os.path.join(item_path, "login"), # 子目录中的备选路径
|
||||||
|
])
|
||||||
|
except (PermissionError, OSError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
login_dir = None
|
||||||
|
for path in possible_login_paths:
|
||||||
|
print(f"[DEBUG] 检查路径: {path}")
|
||||||
|
if os.path.exists(path):
|
||||||
|
login_dir = path
|
||||||
|
print(f"[DEBUG] 找到登录目录: {login_dir}")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not login_dir:
|
||||||
|
return {
|
||||||
|
"current_account": None,
|
||||||
|
"latest_time": None,
|
||||||
|
"message": f"未找到登录信息目录,尝试的路径: {possible_login_paths}"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 遍历登录目录下的所有账号文件夹
|
||||||
|
items = os.listdir(login_dir)
|
||||||
|
print(f"[DEBUG] 登录目录内容: {items}")
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
item_path = os.path.join(login_dir, item)
|
||||||
|
print(f"[DEBUG] 检查项目: {item}, 路径: {item_path}, 是否为目录: {os.path.isdir(item_path)}")
|
||||||
|
|
||||||
|
if not os.path.isdir(item_path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查key_info.db文件
|
||||||
|
key_info_path = os.path.join(item_path, "key_info.db")
|
||||||
|
print(f"[DEBUG] 检查key_info.db文件: {key_info_path}, 是否存在: {os.path.exists(key_info_path)}")
|
||||||
|
|
||||||
|
if not os.path.exists(key_info_path):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 获取文件修改时间
|
||||||
|
try:
|
||||||
|
file_time = os.path.getmtime(key_info_path)
|
||||||
|
file_datetime = datetime.fromtimestamp(file_time)
|
||||||
|
print(f"[DEBUG] 找到key_info.db文件: {key_info_path}, 修改时间: {file_datetime}")
|
||||||
|
|
||||||
|
# 更新最新登录的账号
|
||||||
|
if latest_time is None or file_time > latest_time:
|
||||||
|
latest_time = file_time
|
||||||
|
current_account = item
|
||||||
|
print(f"[DEBUG] 更新最新登录账号: {current_account}, 时间: {file_datetime}")
|
||||||
|
|
||||||
|
except OSError as e:
|
||||||
|
print(f"[DEBUG] 无法获取文件时间: {key_info_path}, 错误: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
except (PermissionError, OSError) as e:
|
||||||
|
print(f"[DEBUG] 无法访问登录目录: {login_dir}, 错误: {e}")
|
||||||
|
return {
|
||||||
|
"current_account": None,
|
||||||
|
"latest_time": None,
|
||||||
|
"message": f"无法访问登录目录: {e}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if current_account:
|
||||||
|
print(f"[DEBUG] 最终结果: 当前登录账号 {current_account}, 时间 {latest_time}")
|
||||||
|
return {
|
||||||
|
"current_account": current_account,
|
||||||
|
"latest_time": latest_time,
|
||||||
|
"latest_time_formatted": datetime.fromtimestamp(latest_time).isoformat() if latest_time else None,
|
||||||
|
"message": f"检测到当前登录账号: {current_account}"
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
print(f"[DEBUG] 最终结果: 未检测到当前登录账号")
|
||||||
|
return {
|
||||||
|
"current_account": None,
|
||||||
|
"latest_time": None,
|
||||||
|
"message": "未检测到当前登录账号"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_wechat_info() -> Dict[str, Any]:
|
def get_wechat_info() -> Dict[str, Any]:
|
||||||
"""获取微信安装和数据库信息
|
"""获取微信安装和数据库信息
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user