Commit d7eda540e20c8d8c4f43e94d62e77f0aec57c432
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 | ... | ... |