Skip to content

Commit 251c61f

Browse files
committed
fix(decrypt): 修复错误账号目录导致的坏库读取问题
- 修正 db_storage 路径扫描逻辑,避免将多账号上级目录误识别为单个账号 - 统一普通解密和流式解密的账号识别与来源写入逻辑 - 为聊天和媒体账号列表增加 SQLite 文件头校验 - 跳过损坏的解密目录,避免聊天页和头像接口继续读取坏库
1 parent 501a07a commit 251c61f

4 files changed

Lines changed: 225 additions & 115 deletions

File tree

src/wechat_decrypt_tool/chat_helpers.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@
2424

2525
_OUTPUT_DATABASES_DIR = get_output_databases_dir()
2626
_DEBUG_SESSIONS = os.environ.get("WECHAT_TOOL_DEBUG_SESSIONS", "0") == "1"
27+
_SQLITE_HEADER = b"SQLite format 3\x00"
28+
29+
30+
def _is_valid_decrypted_sqlite(path: Path) -> bool:
31+
try:
32+
if not path.exists() or (not path.is_file()):
33+
return False
34+
with path.open("rb") as f:
35+
return f.read(len(_SQLITE_HEADER)) == _SQLITE_HEADER
36+
except Exception:
37+
return False
2738

2839

2940
def _list_decrypted_accounts() -> list[str]:
@@ -34,7 +45,7 @@ def _list_decrypted_accounts() -> list[str]:
3445
for p in _OUTPUT_DATABASES_DIR.iterdir():
3546
if not p.is_dir():
3647
continue
37-
if (p / "session.db").exists() and (p / "contact.db").exists():
48+
if _is_valid_decrypted_sqlite(p / "session.db") and _is_valid_decrypted_sqlite(p / "contact.db"):
3849
accounts.append(p.name)
3950

4051
accounts.sort()
@@ -49,7 +60,9 @@ def _resolve_account_dir(account: Optional[str]) -> Path:
4960
detail="No decrypted databases found. Please decrypt first.",
5061
)
5162

52-
selected = account or accounts[0]
63+
selected = str(account or "").strip() or accounts[0]
64+
if selected not in accounts:
65+
raise HTTPException(status_code=404, detail="Account not found.")
5366
base = _OUTPUT_DATABASES_DIR.resolve()
5467
candidate = (_OUTPUT_DATABASES_DIR / selected).resolve()
5568

src/wechat_decrypt_tool/media_helpers.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@
2424

2525
# 运行时输出目录(桌面端可通过 WECHAT_TOOL_DATA_DIR 指向可写目录)
2626
_PACKAGE_ROOT = Path(__file__).resolve().parent
27+
_SQLITE_HEADER = b"SQLite format 3\x00"
28+
29+
30+
def _is_valid_decrypted_sqlite(path: Path) -> bool:
31+
try:
32+
if not path.exists() or (not path.is_file()):
33+
return False
34+
with path.open("rb") as f:
35+
return f.read(len(_SQLITE_HEADER)) == _SQLITE_HEADER
36+
except Exception:
37+
return False
2738

2839

2940
def _list_decrypted_accounts() -> list[str]:
@@ -36,7 +47,7 @@ def _list_decrypted_accounts() -> list[str]:
3647
for p in output_db_dir.iterdir():
3748
if not p.is_dir():
3849
continue
39-
if (p / "session.db").exists() and (p / "contact.db").exists():
50+
if _is_valid_decrypted_sqlite(p / "session.db") and _is_valid_decrypted_sqlite(p / "contact.db"):
4051
accounts.append(p.name)
4152

4253
accounts.sort()
@@ -53,7 +64,9 @@ def _resolve_account_dir(account: Optional[str]) -> Path:
5364
detail="No decrypted databases found. Please decrypt first.",
5465
)
5566

56-
selected = account or accounts[0]
67+
selected = str(account or "").strip() or accounts[0]
68+
if selected not in accounts:
69+
raise HTTPException(status_code=404, detail="Account not found.")
5770
base = output_db_dir.resolve()
5871
candidate = (output_db_dir / selected).resolve()
5972

src/wechat_decrypt_tool/routers/decrypt.py

Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from ..logging_config import get_logger
1515
from ..path_fix import PathFixRoute
1616
from ..key_store import upsert_account_keys_in_store
17-
from ..wechat_decrypt import WeChatDatabaseDecryptor, decrypt_wechat_databases
17+
from ..wechat_decrypt import WeChatDatabaseDecryptor, decrypt_wechat_databases, scan_account_databases_from_path
1818

1919
logger = get_logger(__name__)
2020

@@ -79,6 +79,8 @@ async def decrypt_databases(request: DecryptRequest):
7979
"account_results": results.get("account_results", {}),
8080
}
8181

82+
except HTTPException:
83+
raise
8284
except Exception as e:
8385
logger.error(f"解密API异常: {str(e)}")
8486
raise HTTPException(status_code=500, detail=str(e))
@@ -126,44 +128,17 @@ async def generate_progress():
126128
yield _sse({"type": "scanning", "message": "正在扫描数据库文件..."})
127129
await asyncio.sleep(0)
128130

129-
account_name = "unknown_account"
130-
path_parts = storage_path.parts
131-
account_patterns = ["wxid_"]
132-
for part in path_parts:
133-
for pattern in account_patterns:
134-
if part.startswith(pattern):
135-
parts = part.split("_")
136-
if len(parts) >= 3:
137-
account_name = "_".join(parts[:-1])
138-
else:
139-
account_name = part
140-
break
141-
if account_name != "unknown_account":
142-
break
143-
144-
if account_name == "unknown_account":
145-
for part in reversed(path_parts):
146-
if part != "db_storage" and len(part) > 3:
147-
account_name = part
148-
break
149-
150-
databases: list[dict] = []
151-
for root, _dirs, files in os.walk(storage_path):
152-
if "db_storage" not in str(root):
153-
continue
154-
for file_name in files:
155-
if not file_name.endswith(".db"):
156-
continue
157-
if file_name in ["key_info.db"]:
158-
continue
159-
db_path = os.path.join(root, file_name)
160-
databases.append({"path": db_path, "name": file_name, "account": account_name})
161-
162-
if not databases:
163-
yield _sse({"type": "error", "message": "未找到微信数据库文件!请检查 db_storage_path 是否正确"})
131+
scan_result = scan_account_databases_from_path(p)
132+
if scan_result["status"] == "error":
133+
payload = {"type": "error", "message": scan_result["message"]}
134+
detected_accounts = scan_result.get("detected_accounts") or []
135+
if detected_accounts:
136+
payload["detected_accounts"] = detected_accounts
137+
yield _sse(payload)
164138
return
165139

166-
account_databases = {account_name: databases}
140+
account_databases = scan_result.get("account_databases", {})
141+
account_sources = scan_result.get("account_sources", {})
167142
total_databases = sum(len(dbs) for dbs in account_databases.values())
168143

169144
yield _sse({"type": "start", "total": total_databases, "message": f"开始解密 {total_databases} 个数据库"})
@@ -193,12 +168,9 @@ async def generate_progress():
193168

194169
# Save a hint for later UI (same as non-stream endpoint).
195170
try:
196-
source_db_storage_path = p
197-
wxid_dir = ""
198-
if storage_path.name.lower() == "db_storage":
199-
wxid_dir = str(storage_path.parent)
200-
else:
201-
wxid_dir = str(storage_path)
171+
source_info = account_sources.get(account, {})
172+
source_db_storage_path = str(source_info.get("db_storage_path") or p)
173+
wxid_dir = str(source_info.get("wxid_dir") or "")
202174
(account_output_dir / "_source.json").write_text(
203175
json.dumps({"db_storage_path": source_db_storage_path, "wxid_dir": wxid_dir}, ensure_ascii=False, indent=2),
204176
encoding="utf-8",

0 commit comments

Comments
 (0)