فهرست منبع

Name DM partners and quote room names in the joined-rooms summary

DM rooms carry no usable name of their own, so the summary printed bare
room IDs. Resolve the partner's login once per room from the member
directory (best-effort, cached per session) and quote real room names,
so "/join"-style replies become copyable and readable.
Paul Klumpp 1 هفته پیش
والد
کامیت
f464408798
2فایلهای تغییر یافته به همراه62 افزوده شده و 6 حذف شده
  1. 33 2
      adapter.py
  2. 29 4
      test_adapter.py

+ 33 - 2
adapter.py

@@ -317,6 +317,9 @@ class ChattoAdapter(BasePlatformAdapter):
         # only for [universal] tags in the joined-rooms log line, never for
         # gating.
         self._universal_room_ids: set[str] = set()
+        # DM room ID -> chat partner's login, resolved once via the member
+        # directory so the joined-rooms summary can name who a DM is with.
+        self._dm_partners: dict[str, str] = {}
         # One-shot guard for the unjoined-home-channel warning in _refresh_rooms.
         self._home_warning_logged = False
         self._ws_task: asyncio.Task | None = None
@@ -1596,16 +1599,42 @@ class ChattoAdapter(BasePlatformAdapter):
             home_id,
         )
 
+    async def _resolve_dm_partner(self, client: ChattoClient, room_id: str) -> None:
+        """Cache the chat partner's login for a DM room, best-effort.
+
+        DM rooms carry no usable name of their own, so the joined-rooms
+        summary names the other side instead. Resolved once per room and
+        session; without our own user (pre-connect) or on a directory error
+        the raw room name stays in place.
+        """
+        if room_id in self._dm_partners or self.me is None:
+            return
+        try:
+            members, _page = await client.list_room_members(room_id)
+        except Exception:
+            logger.debug(
+                "Chatto: could not list members of DM %s", room_id, exc_info=True
+            )
+            return
+        for member in members:
+            user = member.user
+            if user and user.id != self.me.id:
+                self._dm_partners[room_id] = user.login or user.display_name
+                return
+
     def _joined_room_label(self, room_id: str) -> str:
         """One entry for the joined-rooms summary, with how-we-answer tags.
 
         The tag names the room's participation mode at a glance: DMs answer
         unconditionally, listed channels answer on mentions or every message,
-        and an unlisted channel is read-only.
+        and an unlisted channel is read-only. A DM is labelled with its chat
+        partner's login rather than its (empty) room name, and names are
+        quoted so empty strings and spaces stay visible.
         """
         name = self._room_names.get(room_id, room_id)
         if self._room_kinds.get(room_id) == RoomKind.DM:
             tags = "[dm]"
+            name = self._dm_partners.get(room_id) or name
         else:
             policy = self._room_policy(room_id)
             if policy == RoomPolicy.REQUIRE_MENTION:
@@ -1616,7 +1645,7 @@ class ChattoAdapter(BasePlatformAdapter):
                 tags = "[read-only]"
         if room_id in self._universal_room_ids:
             tags += " [universal]"
-        return f"{name} ({room_id}) {tags}"
+        return f'"{name}" ({room_id}) {tags}'
 
     def _log_joined_rooms(self) -> None:
         """Log the joined rooms and how the bot answers in each of them."""
@@ -1649,6 +1678,8 @@ class ChattoAdapter(BasePlatformAdapter):
 
                 self._room_names[room_obj.id] = room_obj.name
                 self._room_kinds[room_obj.id] = room_obj.kind
+                if room_obj.kind == RoomKind.DM:
+                    await self._resolve_dm_partner(client, room_obj.id)
                 if room_obj.universal:
                     self._universal_room_ids.add(room_obj.id)
 

+ 29 - 4
test_adapter.py

@@ -47,6 +47,7 @@ from chattolib.types import (
     DirectoryMember,
     Message,
     MessageAttachment,
+    Page,
     PresenceStatus,
     Room,
     RoomKind,
@@ -2101,10 +2102,34 @@ class TestSilentRoomRefresh:
         joined = [m for m in caplog.messages if "currently joined" in m]
         assert joined, "expected the joined-rooms summary log line"
         summary = joined[-1]
-        assert "News (news-1) [read-only] [universal]" in summary
-        assert "Team (team-1)" in summary and "[every-message]" in summary
-        assert "Deploy (dep-1) [on-mention]" in summary
-        assert "Paula (dm-1) [dm]" in summary
+        assert '"News" (news-1) [read-only] [universal]' in summary
+        assert '"Team" (team-1)' in summary and "[every-message]" in summary
+        assert '"Deploy" (dep-1) [on-mention]' in summary
+        assert '"Paula" (dm-1) [dm]' in summary
+
+    async def test_join_log_shows_dm_partner_username(self, caplog):
+        adapter = self._adapter([])
+        adapter.me = User(id="bot-1", login="hermes", display_name="Hermes")
+        directory: dict[str, list[DirectoryMember]] = {
+            "dm-1": [
+                DirectoryMember(user=adapter.me),
+                DirectoryMember(user=_make_user("user-9", "paula")),
+            ]
+        }
+
+        async def list_members(room_id, **_kwargs):
+            return directory.get(room_id, []), Page()
+
+        adapter._chatto_client.list_room_members = AsyncMock(side_effect=list_members)
+        dm = _make_room_state(_make_room("dm-1", "", RoomKind.DM), True)
+        adapter._chatto_client.list_rooms = AsyncMock(return_value=[dm])
+
+        with caplog.at_level("INFO"):
+            await adapter._refresh_rooms()
+
+        joined = [m for m in caplog.messages if "currently joined" in m]
+        assert joined, "expected the joined-rooms summary log line"
+        assert '"paula" (dm-1) [dm]' in joined[-1]
 
     async def test_join_log_appears_on_every_refresh(self, caplog):
         adapter = self._adapter(["team-1"])