* fix(image): scope local_id lookup by chat_id + use real column name
`ImageResolver.get_image_md5` made two wrong assumptions about the
production `MessageResourceInfo` schema, which made the decode_image
MCP tool always fail with "无法找到 local_id=X 的图片信息":
1. The column is `message_local_id`, not `local_id`. The current query
throws `sqlite3.OperationalError: no such column: local_id`, but the
exception is swallowed by `except Exception: pass`, masking the real
failure as a silent miss.
2. `message_local_id` is not globally unique. In production it repeats
across chats, and within an active chat the same local_id can recur
up to 7 times (observed on a real DB). The production schema scopes
by `chat_id`, resolved from `ChatName2Id.rowid WHERE user_name = ?`.
Fix: `get_image_md5` now takes `(username, local_id)`:
- Resolve `username -> chat_id` via `ChatName2Id`.
- Query `MessageResourceInfo` filtered by `chat_id + message_local_id
+ message_local_type == 3` (image type; high bits are session flags,
so use `% 2^32`), ordered by `message_create_time DESC LIMIT 1`.
External callers (`mcp_server.decode_image_tool` /
`list_chat_images_tool`) already pass `username` through
`ImageResolver.decode_image()` / `list_chat_images()`, so the public
API is unchanged. Only the internal helper signature shifts.
The existing test fixture in `test_decode_image_v2` used the same wrong
schema as the buggy code (`CREATE TABLE MessageResourceInfo (local_id
INTEGER PRIMARY KEY, packed_info BLOB)`), so the tests passed against a
self-consistent fiction. The fixture is rebuilt to match real columns
plus `ChatName2Id`, and three regression tests are added:
- cross-chat collision (same local_id in 3 chats; must pick the right one
and not the type=43 video row)
- same-chat reuse (same local_id, two timestamps; must pick the newer)
- unknown chat (username not in ChatName2Id; structured error, no crash)
All 154 tests pass locally (151 baseline + 3 new).
* fix(image): surface get_image_md5 errors and use read-only DB open
Two follow-ups on top of the chat_id scoping fix:
1. The bare `except Exception: pass` was the original failure mode: it
silently swallowed `OperationalError: no such column: local_id` when
the production schema diverged from the old `local_id` column name,
masking the bug this PR fixes. Print the exception to stderr so
future schema drift surfaces immediately instead of returning a
misleading "image not found" error.
2. Open message_resource.db with `file:...?mode=ro` URI to match the
rest of the project (monitor_web.py uses this idiom in 9 places).
The DB is read-only for our purposes and a running WeChat may still
hold it; using URI ro avoids any chance of lock contention.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>