Commit d7eda540e20c8d8c4f43e94d62e77f0aec57c432

Authored by yibo
1 parent 7ea91bae

fix(phoneuse): WeChatConnector 按当前登录 uin 定位账号目录+推导 key

旧逻辑只在缓存为空时按字典序取第一个账号目录、写死单一 SQLCipher key,
第二个账号登录后(目录名字典序更靠后)会静默套错账号+错误 key 导致读取失败。
改成每次都用 last_login_uin 算 MD5("mm"+uin) 定位目录、MD5(imei+uin)[:7] 推导 key,
账号切换时自动刷新缓存,公式与 lifelog-recorder/WeChatDb.kt 保持一致。

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Showing 1 changed file with 41 additions and 12 deletions
phoneuse/connectors.py
... ... @@ -134,29 +134,58 @@ class WeChatConnector(BaseConnector):
134 134 APP = "wechat"
135 135  
136 136 PACKAGE = "com.tencent.mm"
137   - CIPHER_KEY = "e03ce81"
  137 + CIPHER_KEY = "e03ce81" # fallback only — real key is derived per-account, see _discover_db_path
138 138  
139 139 def __init__(self):
140 140 super().__init__()
141   - self._db_path = self._kb.get("db_path", "")
142   - if not self._db_path:
143   - self._db_path = self._discover_db_path()
144   - if self._db_path:
145   - kb = self._kb
146   - kb["db_path"] = self._db_path
147   - _save_kb(self.APP, kb)
148   -
149   - def _discover_db_path(self) -> str:
  141 + # 每次都重新按当前登录的 uin 定位账号目录+key(便宜:一次 su grep),
  142 + # 跟缓存值不一致(换过账号)就刷新缓存。旧版只在缓存为空时按字典序取第一个目录
  143 + # 名字+写死 key,换账号后会静默套错账号(正是这次 jimmy 账号读不到的根因)。
  144 + current_path, current_key = self._discover_db_path()
  145 + cached_path = self._kb.get("db_path", "")
  146 + if current_path and current_path != cached_path:
  147 + self._db_path = current_path
  148 + self.CIPHER_KEY = current_key
  149 + kb = self._kb
  150 + kb["db_path"] = current_path
  151 + kb["cipher_key"] = current_key
  152 + _save_kb(self.APP, kb)
  153 + else:
  154 + self._db_path = cached_path or current_path
  155 + self.CIPHER_KEY = self._kb.get("cipher_key", current_key or self.CIPHER_KEY)
  156 +
  157 + def _discover_db_path(self) -> tuple[str, str]:
  158 + """定位当前登录账号的库+推导 key。key 公式(真机验证):
  159 + MD5("1234567890ABCDEF"+uin)[:7];账号目录名 = MD5("mm"+uin)。见 lifelog-recorder 的 WeChatDb.kt。
  160 + """
  161 + import hashlib
  162 + try:
  163 + uin = self._run_adb(
  164 + "grep -hoE 'last_login_uin\">[0-9-]+' "
  165 + "/data/data/com.tencent.mm/shared_prefs/*.xml 2>/dev/null "
  166 + "| grep -oE '[0-9-]+' | head -1"
  167 + ).strip()
  168 + if uin:
  169 + dir_hash = hashlib.md5(f"mm{uin}".encode()).hexdigest()
  170 + db_path = f"/data/data/com.tencent.mm/MicroMsg/{dir_hash}/EnMicroMsg.db"
  171 + exists = self._run_adb(f"[ -f {db_path} ] && echo yes || echo no").strip()
  172 + if "yes" in exists:
  173 + key = hashlib.md5(("1234567890ABCDEF" + uin).encode()).hexdigest()[:7]
  174 + return db_path, key
  175 + except Exception:
  176 + pass
  177 + # 兜底:读不到 uin(root 不可用/微信没登录)时退回旧的字典序取第一个目录,
  178 + # 用默认 key(只有单账号且是最早那个账号时才可能凑巧对)。
150 179 try:
151 180 result = self._run_adb(
152 181 "ls /data/data/com.tencent.mm/MicroMsg/ 2>/dev/null | head -1"
153 182 )
154 183 for line in result.splitlines():
155 184 if len(line.strip()) == 32: # MD5 hash directory
156   - return f"/data/data/com.tencent.mm/MicroMsg/{line.strip()}/EnMicroMsg.db"
  185 + return f"/data/data/com.tencent.mm/MicroMsg/{line.strip()}/EnMicroMsg.db", self.CIPHER_KEY
157 186 except Exception:
158 187 pass
159   - return ""
  188 + return "", self.CIPHER_KEY
160 189  
161 190 def _sqlcipher(self, sql: str) -> list[dict]:
162 191 db_path = self._db_path
... ...