diff --git a/src/controller/blueski/handler.py b/src/controller/blueski/handler.py index bdd79f27..c99b9cec 100644 --- a/src/controller/blueski/handler.py +++ b/src/controller/blueski/handler.py @@ -95,6 +95,16 @@ class Handler: start=False, kwargs=dict(parent=controller.view.nb, name="mentions", session=session, sound="mention_received.ogg") ) + # Chats + pub.sendMessage( + "createBuffer", + buffer_type="ConversationListBuffer", + session_type="blueski", + buffer_title=_("Chats"), + parent_tab=root_position, + start=False, + kwargs=dict(parent=controller.view.nb, name="direct_messages", session=session, sound="dm_received.ogg") + ) # Notifications pub.sendMessage( "createBuffer", @@ -155,16 +165,6 @@ class Handler: start=False, kwargs=dict(parent=controller.view.nb, name="blocked", session=session) ) - # Chats - pub.sendMessage( - "createBuffer", - buffer_type="ConversationListBuffer", - session_type="blueski", - buffer_title=_("Chats"), - parent_tab=root_position, - start=False, - kwargs=dict(parent=controller.view.nb, name="direct_messages", session=session, sound="dm_received.ogg") - ) # Timelines container pub.sendMessage( diff --git a/src/controller/buffers/blueski/base.py b/src/controller/buffers/blueski/base.py index 2efd6abf..24173bae 100644 --- a/src/controller/buffers/blueski/base.py +++ b/src/controller/buffers/blueski/base.py @@ -1046,13 +1046,13 @@ class BaseBuffer(base.Buffer): author = it.get("author") if isinstance(author, dict): return author.get("did") or author.get("handle") - # Chat message fallback + # Chat message fallback — use str() to ensure stable hash/comparison sent_at = it.get("sentAt") or it.get("sent_at") or it.get("createdAt") or it.get("created_at") sender = it.get("sender") or (nested.get("sender") if isinstance(nested, dict) else {}) or {} sender_did = sender.get("did") if isinstance(sender, dict) else None text = it.get("text") or (nested.get("text") if isinstance(nested, dict) else None) if sent_at or sender_did or text: - return (sent_at, sender_did, text) + return (str(sent_at) if sent_at else None, str(sender_did) if sender_did else None, str(text) if text else None) return None post = getattr(it, "post", None) if post is not None: @@ -1075,7 +1075,7 @@ class BaseBuffer(base.Buffer): sender_did = getattr(sender, "did", None) if sender is not None else None text = getattr(it, "text", None) or (getattr(nested, "text", None) if nested is not None else None) if sent_at or sender_did or text: - return (sent_at, sender_did, text) + return (str(sent_at) if sent_at else None, str(sender_did) if sender_did else None, str(text) if text else None) return None for item in self.session.db[self.name]: diff --git a/src/controller/buffers/blueski/chat.py b/src/controller/buffers/blueski/chat.py index a238e67a..b51ac948 100644 --- a/src/controller/buffers/blueski/chat.py +++ b/src/controller/buffers/blueski/chat.py @@ -95,21 +95,21 @@ class ConversationListBuffer(BaseBuffer): for attr in ("id", "messageId", "message_id", "msgId", "msg_id", "cid", "rev"): val = g(last_msg, attr) if val: - return val + return str(val) nested = g(last_msg, "message") or g(last_msg, "record") if nested: for attr in ("id", "messageId", "message_id", "msgId", "msg_id", "cid", "rev"): val = g(nested, attr) if val: - return val + return str(val) sent_at = g(last_msg, "sentAt") or g(last_msg, "sent_at") or g(last_msg, "createdAt") or g(last_msg, "created_at") sender = g(last_msg, "sender") or (g(nested, "sender") if nested else {}) or {} sender_did = g(sender, "did") text = g(last_msg, "text") or (g(nested, "text") if nested else None) if sent_at or sender_did or text: - return (sent_at, sender_did, text) + return (str(sent_at) if sent_at else None, str(sender_did) if sender_did else None, str(text) if text else None) return None def get_formatted_message(self): @@ -181,12 +181,33 @@ class ConversationListBuffer(BaseBuffer): try: res = self.session.list_convos(limit=count) items = res.get("items", []) + self._build_member_maps(items) return self._merge_conversations(items, play_sound, avoid_autoreading=avoid_autoreading) except Exception: log.exception("Error fetching conversations") output.speak(_("Error loading conversations."), True) return 0 + def _build_member_maps(self, convos): + """Build DID→name maps from conversation members and store in db for chat buffers.""" + def g(obj, key, default=None): + if isinstance(obj, dict): + return obj.get(key, default) + return getattr(obj, key, default) + for convo in convos: + convo_id = self.get_convo_id(convo) + if not convo_id: + continue + members = g(convo, "members", []) or [] + member_map = {} + for m in members: + did = g(m, "did", None) + if did: + name = g(m, "display_name") or g(m, "displayName") or g(m, "handle", "unknown") + member_map[did] = name + if member_map: + self.session.db["convo_" + str(convo_id) + "_members"] = member_map + def _merge_conversations(self, items, play_sound=True, avoid_autoreading=False): """Merge conversation list, updating items without duplicating or re-alerting.""" if self.session.db.get(self.name) is None: @@ -395,14 +416,34 @@ class ChatBuffer(BaseBuffer): self.type = "chat_messages" self.convo_id = kwargs.get("convo_id") self.sound = "dm_received.ogg" + self._member_map_loaded = False def create_buffer(self, parent, name): self.buffer = BlueskiPanels.ChatMessagePanel(parent, name) self.buffer.session = self.session + def _update_member_map(self): + """Fetch conversation members to build a DID-to-name map for sender resolution.""" + try: + convo = self.session.get_convo(self.convo_id) + if not convo: + return + member_map = {} + for m in getattr(convo, "members", []) or []: + did = getattr(m, "did", None) + if did: + name = getattr(m, "display_name", None) or getattr(m, "handle", None) or "unknown" + member_map[did] = name + self.session.db[self.name + "_members"] = member_map + except Exception: + log.exception("Error fetching conversation members for DID resolution") + def start_stream(self, mandatory=False, play_sound=True): if not self.convo_id: return 0 + if not self._member_map_loaded: + self._update_member_map() + self._member_map_loaded = True count = self.get_max_items() try: res = self.session.get_convo_messages(self.convo_id, limit=count) diff --git a/src/sessions/blueski/compose.py b/src/sessions/blueski/compose.py index 18f030fa..a87bf630 100644 --- a/src/sessions/blueski/compose.py +++ b/src/sessions/blueski/compose.py @@ -366,17 +366,25 @@ def compose_convo(convo, db, settings, relative_times, show_screen_names=False, members = g(convo, "members", []) self_did = db.get("user_id") if isinstance(db, dict) else None + # Build a local DID→name map from conversation members for sender resolution + member_names = {} + for m in members: + did = g(m, "did", None) + if did: + name = g(m, "display_name") or g(m, "displayName") or g(m, "handle", "unknown") + member_names[did] = name + # Get other participants (exclude self) others = [] for m in members: did = g(m, "did", None) if self_did and did == self_did: continue - label = g(m, "displayName") or g(m, "display_name") or g(m, "handle", "unknown") + label = member_names.get(did, "unknown") if did else g(m, "display_name") or g(m, "displayName") or g(m, "handle", "unknown") others.append(label) if not others: - others = [g(m, "displayName") or g(m, "display_name") or g(m, "handle", "unknown") for m in members] + others = [member_names.get(g(m, "did"), "unknown") if g(m, "did") else "unknown" for m in members] participants = ", ".join(others) @@ -389,7 +397,14 @@ def compose_convo(convo, db, settings, relative_times, show_screen_names=False, last_text = g(last_msg_obj, "text", "") sender = g(last_msg_obj, "sender", None) if sender: - last_sender = g(sender, "displayName") or g(sender, "display_name") or g(sender, "handle", "") + last_sender = g(sender, "display_name") or g(sender, "displayName") or g(sender, "handle") + if not last_sender: + # Resolve DID via local member map + sdid = g(sender, "did") + if sdid: + last_sender = member_names.get(sdid, "") + if not last_sender: + last_sender = sdid or "" # Date date_str = "" @@ -434,7 +449,16 @@ def compose_chat_message(msg, db, settings, relative_times, show_screen_names=Fa return getattr(obj, key, default) sender = g(msg, "sender", {}) - handle = g(sender, "displayName") or g(sender, "display_name") or g(sender, "handle", "unknown") + sender_did = g(sender, "did") + handle = g(sender, "display_name") or g(sender, "displayName") or g(sender, "handle") + if not handle and sender_did and isinstance(db, dict): + # Look up DID in member maps stored by ChatBuffer + for key, val in db.items(): + if key.endswith("_members") and isinstance(val, dict) and sender_did in val: + handle = val[sender_did] + break + if not handle: + handle = sender_did or "unknown" text = g(msg, "text", "") diff --git a/src/sessions/blueski/session.py b/src/sessions/blueski/session.py index a23e82e6..8e5810fe 100644 --- a/src/sessions/blueski/session.py +++ b/src/sessions/blueski/session.py @@ -751,6 +751,18 @@ class Session(base.baseSession): log.exception("Error listing conversations") return {"items": [], "cursor": None} + def get_convo(self, convo_id: str): + """Fetch a single conversation by ID, returning the convo object or None.""" + api = self._ensure_client() + dm_client = api.with_bsky_chat_proxy() + dm = dm_client.chat.bsky.convo + try: + res = dm.get_convo({"convoId": convo_id}) + return res.convo + except Exception: + log.exception("Error fetching conversation %s", convo_id) + return None + def get_convo_messages(self, convo_id: str, limit: int = 50, cursor: str | None = None) -> dict[str, Any]: api = self._ensure_client() dm_client = api.with_bsky_chat_proxy()