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:
Belugary
2026-05-14 16:40:49 +08:00
committed by ylytdeng
parent b98641347c
commit 1aa12c86fa

View File

@@ -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: