fix(monitor_web): use full message content, not truncated session summary (#42)
## Problem In `--web` mode, messages longer than ~90 chars get truncated in the SSE feed. `SessionMonitor.check_updates()` pushes `SessionTable.summary` to clients, but `summary` is WeChat's own ~80-char preview kept for the client-side chat list — not the full message body. ## Fix `_lookup_latest_local_id` already hits `Msg_<md5(username)>` for the row at `(username, create_time)` to obtain `local_id` for #79's dedup. Extend the same query to also return `message_content` and `WCDB_CT_message_content`, and use it to replace `summary` when the DB body is longer: SELECT local_id, message_content, WCDB_CT_message_content FROM [Msg_<md5>] WHERE create_time = ? ORDER BY local_id DESC LIMIT 1 Same row, same query — zero additional IO vs. the prior `MAX(local_id)`. Renamed to `_lookup_latest_message` to reflect the new `(local_id, content)` return shape. zstd handling and `wxid_xxx:\n` group-prefix stripping mirror the existing `SessionTable.summary` logic in `check_updates`, so the SSE `content` field stays in the same format clients already render. Replacement is conservative — only swaps in `full_content` when strictly longer than `summary`. This never shortens existing behavior and degrades cleanly if the message-DB write hasn't landed yet (the SessionTable-vs-message_N.db timing race that #79 already documented). ## Scope - `monitor_web.py`: one helper extended + one call site adjusted. No schema change, no new dependency, no client/UI change. - `_check_hidden_messages` cold path is untouched — its same-second multi-message coverage still runs as before.
This commit is contained in:
@@ -935,21 +935,24 @@ class SessionMonitor:
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def _lookup_latest_local_id(self, username, timestamp):
|
||||
"""从 message_N.db 查指定 username 在 timestamp 的最大 local_id。
|
||||
def _lookup_latest_message(self, username, timestamp):
|
||||
"""从 message_N.db 查指定 username 在 timestamp 的最新一条消息,返回
|
||||
(local_id, message_content)。
|
||||
|
||||
SessionTable 触发推送时调用此方法拿到对应 local_id,加到 _shown_keys 后
|
||||
`_check_hidden_messages` 路径能用 (username, local_id) 精确去重,避免 issue #79
|
||||
的"同秒同类型多条消息 10 丢 4"。
|
||||
SessionTable 推送时调用:
|
||||
- local_id 加入 _shown_keys,供 `_check_hidden_messages` 精确去重 (issue #79)
|
||||
- message_content 用于替换 SessionTable.summary 的 ~80 字短截断 (issue #42)
|
||||
|
||||
时机风险:SessionTable 写入比 message DB 早几毫秒,可能查不到。查不到时返回 None,
|
||||
调用方应选择跳过加 key(让 hidden 路径稍后补救并自己加 key)。
|
||||
两者本就同行,合并到一次 SELECT,相比原 MAX(local_id) 不增加 IO。
|
||||
|
||||
时机风险:SessionTable 写入比 message DB 早几毫秒,可能查不到。查不到时返回
|
||||
(None, None),调用方跳过加 key,由 `_check_hidden_messages` 兜底。
|
||||
"""
|
||||
if not self.db_cache or not self.username_db_map:
|
||||
return None
|
||||
return None, None
|
||||
db_keys = self.username_db_map.get(username, [])
|
||||
if not db_keys:
|
||||
return None
|
||||
return None, None
|
||||
table_name = f"Msg_{hashlib.md5(username.encode()).hexdigest()}"
|
||||
for db_key in db_keys:
|
||||
dec_path = self.db_cache.get(db_key)
|
||||
@@ -958,14 +961,28 @@ class SessionMonitor:
|
||||
try:
|
||||
with closing(sqlite3.connect(f"file:{dec_path}?mode=ro&immutable=1", uri=True)) as conn:
|
||||
row = conn.execute(
|
||||
f"SELECT MAX(local_id) FROM [{table_name}] WHERE create_time = ?",
|
||||
f"SELECT local_id, message_content, WCDB_CT_message_content "
|
||||
f"FROM [{table_name}] WHERE create_time = ? "
|
||||
f"ORDER BY local_id DESC LIMIT 1",
|
||||
(timestamp,),
|
||||
).fetchone()
|
||||
if row and row[0]:
|
||||
return row[0]
|
||||
local_id, mc, ct = row
|
||||
if isinstance(mc, bytes) and ct == 4:
|
||||
try:
|
||||
mc = _zstd_dctx.decompress(mc).decode('utf-8', errors='replace')
|
||||
except Exception:
|
||||
mc = mc.decode('utf-8', errors='replace')
|
||||
elif isinstance(mc, bytes):
|
||||
mc = mc.decode('utf-8', errors='replace')
|
||||
# 群消息 message_content 形如 'wxid_xxx:\n<正文>',与
|
||||
# SessionTable.summary 调用方一致地剥离前缀
|
||||
if mc and ':\n' in mc:
|
||||
mc = mc.split(':\n', 1)[1]
|
||||
return local_id, mc
|
||||
except Exception:
|
||||
continue
|
||||
return None
|
||||
return None, None
|
||||
|
||||
def _check_hidden_messages(self, username, prev_ts, curr_ts, curr_msg_type, display, is_group, sender):
|
||||
"""检查时间窗口内是否有被 session 摘要覆盖的消息(文字、图片、表情等)
|
||||
@@ -1501,12 +1518,16 @@ class SessionMonitor:
|
||||
|
||||
new_msgs.append(msg_data)
|
||||
# _shown_keys 改用 (username, local_id) 精确去重(issue #79)。
|
||||
# SessionTable 不带 local_id,去 message_N.db 查 max(local_id) WHERE create_time=curr_ts。
|
||||
# SessionTable 不带 local_id,去 message_N.db 查同时拿 local_id 和完整正文:
|
||||
# - local_id 用于去重
|
||||
# - 完整正文替换 SessionTable.summary 的 ~80 字短截断(issue #42)
|
||||
# 查不到时(message DB 写入滞后于 SessionTable)跳过加 key,让 _check_hidden_messages
|
||||
# 1 秒后查到时自己 emit 并加 key。这种情况下偶发轻微重复,但比丢消息好。
|
||||
latest_local_id = self._lookup_latest_local_id(username, curr['timestamp'])
|
||||
latest_local_id, full_content = self._lookup_latest_message(username, curr['timestamp'])
|
||||
if latest_local_id is not None:
|
||||
self._shown_keys.add((username, latest_local_id))
|
||||
if full_content and len(full_content) > len(msg_data['content']):
|
||||
msg_data['content'] = full_content
|
||||
|
||||
# 图片消息: 后台异步解密(不阻塞轮询)
|
||||
if curr['msg_type'] == 3:
|
||||
|
||||
Reference in New Issue
Block a user