fix(matrix): record DM rooms in m.direct on invite to prevent group misclassification

Rebase onto plugins/platforms/matrix/adapter.py (code moved from
gateway/platforms/matrix.py). Same logic: _on_invite checks is_direct
on invite events and calls _record_dm_room to persist in m.direct
account data.

Fixes #44679
This commit is contained in:
liuhao1024 2026-06-21 08:29:01 +08:00 committed by Teknium
parent fde1c8570f
commit 14baeefe1d
2 changed files with 325 additions and 5 deletions

View file

@ -2885,15 +2885,27 @@ class MatrixAdapter(BasePlatformAdapter):
await self.handle_message(msg_event)
async def _on_invite(self, event: Any) -> None:
"""Auto-join rooms when invited."""
"""Auto-join rooms when invited, recording DM rooms in m.direct."""
room_id = str(getattr(event, "room_id", ""))
content = getattr(event, "content", None)
is_direct = bool(getattr(content, "is_direct", False))
inviter = str(getattr(event, "sender", ""))
logger.info(
"Matrix: invited to %s — joining",
"Matrix: invited to %s — joining (is_direct=%s)",
room_id,
is_direct,
)
# When the invite declares this as a DM, record it in m.direct after
# the (non-blocking) join completes so that _resolve_room_identity
# treats it correctly even when the bot account has no prior DM
# history. The join itself stays off the sync path.
self._schedule_invite_join(
room_id,
is_direct=is_direct and bool(inviter),
inviter=inviter,
)
self._schedule_invite_join(room_id)
async def _join_room_by_id(self, room_id: str) -> bool:
"""Join a room by ID and refresh local caches on success."""
@ -2913,7 +2925,13 @@ class MatrixAdapter(BasePlatformAdapter):
logger.warning("Matrix: error joining %s: %s", room_id, exc)
return False
def _schedule_invite_join(self, room_id: str) -> None:
def _schedule_invite_join(
self,
room_id: str,
*,
is_direct: bool = False,
inviter: str = "",
) -> None:
"""Schedule an invite join without blocking sync or gateway readiness."""
if not room_id or room_id in self._joined_rooms:
return
@ -2923,7 +2941,13 @@ class MatrixAdapter(BasePlatformAdapter):
async def _join_invite() -> None:
try:
await asyncio.wait_for(self._join_room_by_id(room_id), timeout=45.0)
joined = await asyncio.wait_for(
self._join_room_by_id(room_id), timeout=45.0
)
# Persist the DM signal from the invite once the join lands,
# so m.direct is authoritative even on a fresh bot account.
if joined and is_direct and inviter:
await self._record_dm_room(room_id, inviter)
except asyncio.TimeoutError:
logger.warning("Matrix: timed out joining invite %s", room_id)
finally:
@ -3779,6 +3803,47 @@ class MatrixAdapter(BasePlatformAdapter):
self._room_identities.clear()
self._room_identity_cached_at.clear()
async def _record_dm_room(self, room_id: str, inviter: str) -> None:
"""Persist a room as DM in m.direct account data after an invite.
When the bot account has never been used for DMs, ``m.direct`` is
absent (404). This method fetches the current mapping (if any),
appends *room_id* under the *inviter*'s entry, and writes it back
so that subsequent ``_refresh_dm_cache`` calls treat the room as a
DM without requiring manual ``m.direct`` setup.
"""
if not self._client:
return
dm_data: Dict[str, list] = {}
try:
resp = await self._client.get_account_data("m.direct")
if hasattr(resp, "content") and isinstance(resp.content, dict):
dm_data = resp.content
elif isinstance(resp, dict):
dm_data = resp
except Exception:
pass # m.direct doesn't exist yet — start fresh
rooms_for_user = dm_data.get(inviter, [])
if not isinstance(rooms_for_user, list):
rooms_for_user = []
if room_id not in rooms_for_user:
rooms_for_user.append(room_id)
dm_data[inviter] = rooms_for_user
try:
await self._client.set_account_data("m.direct", dm_data)
logger.info(
"Matrix: recorded %s as DM room (inviter=%s)", room_id, inviter
)
except Exception as exc:
logger.warning("Matrix: failed to update m.direct: %s", exc)
# Update local cache so _resolve_room_identity sees it immediately.
self._dm_rooms[room_id] = True
self._room_identities.pop(room_id, None)
self._room_identity_cached_at.pop(room_id, None)
# ------------------------------------------------------------------
# Mention detection helpers
# ------------------------------------------------------------------