diff --git a/meshcore_gui/ble/events.py b/meshcore_gui/ble/events.py index ef180db..5700d40 100644 --- a/meshcore_gui/ble/events.py +++ b/meshcore_gui/ble/events.py @@ -77,6 +77,16 @@ class EventHandler: names.append(h.upper()) return names + @staticmethod + def _looks_like_hex_identifier(value: str) -> bool: + """Return True when *value* looks like a pubkey/hash prefix.""" + if not value: + return False + probe = str(value).strip() + if len(probe) < 6: + return False + return all(ch in '0123456789abcdefABCDEF' for ch in probe) + # ------------------------------------------------------------------ # RX_LOG_DATA — the single source of truth for path info # ------------------------------------------------------------------ @@ -339,22 +349,33 @@ class EventHandler: is_room_message = txt_type == 2 if is_room_message: + author = '' + explicit_name = ( + payload.get('author') + or payload.get('sender_name') + or payload.get('name') + or '' + ) + if explicit_name and not self._looks_like_hex_identifier(explicit_name): + author = explicit_name + + sender_field = str(payload.get('sender') or '').strip() + if not author and sender_field and not self._looks_like_hex_identifier(sender_field): + author = sender_field + author_key = ( signature or payload.get('sender_pubkey') - or payload.get('sender') or payload.get('author_pubkey') + or (sender_field if self._looks_like_hex_identifier(sender_field) else '') or '' ) - author = '' - if author_key: + if not author and author_key: author = self._shared.get_contact_name_by_prefix(author_key) if not author: author = ( - payload.get('author') - or payload.get('name') - or payload.get('sender_name') - or payload.get('sender') + explicit_name + or sender_field or (author_key[:8] if author_key else '') or '?' ) diff --git a/meshcore_gui/core/shared_data.py b/meshcore_gui/core/shared_data.py index a6db5ef..c69cbc3 100644 --- a/meshcore_gui/core/shared_data.py +++ b/meshcore_gui/core/shared_data.py @@ -77,12 +77,6 @@ class SharedData: # Room Server login states: pubkey → {'state': 'ok'|'fail'|'pending'|'logged_out', 'detail': str} self.room_login_states: Dict[str, Dict] = {} - # Known room pubkeys/prefixes (normalised to 12 hex chars). - # This is a central, UI-independent registry used by the - # MessagesPanel to keep Room Server traffic out of All Messages, - # even when the room cards are not yet fully restored. - self._known_room_pubkeys: set[str] = set() - # Room message cache: pubkey_prefix (12 hex) → List[Message] # Populated from archive on first access per room, then kept in # sync by add_message(). @@ -183,37 +177,9 @@ class SharedData: return self.device.name # ------------------------------------------------------------------ - # Room Server login state / known room registry + # Room Server login state # ------------------------------------------------------------------ - @staticmethod - def _normalise_room_pubkey(pubkey: str) -> str: - """Return a comparable room pubkey prefix. - - Normalises a full Room Server pubkey or a shorter prefix to the - canonical 12-hex representation used throughout the room-cache - logic. Returns an empty string for falsey input. - """ - return (pubkey or '').strip()[:12] - - def register_room_pubkey(self, pubkey: str) -> None: - """Register a Room Server pubkey/prefix as known. - - Keeps the registry small and stable by storing only the - canonical 12-hex prefix. Safe to call repeatedly. - """ - norm = self._normalise_room_pubkey(pubkey) - if not norm: - return - with self.lock: - self._known_room_pubkeys.add(norm) - - def get_known_room_pubkeys(self) -> set[str]: - """Return a copy of all centrally known Room Server keys.""" - with self.lock: - return set(self._known_room_pubkeys) - - def set_room_login_state( self, pubkey_prefix: str, state: str, detail: str = "", ) -> None: @@ -229,14 +195,9 @@ class SharedData: state: One of 'pending', 'ok', 'fail', 'logged_out'. detail: Human-readable detail string. """ - norm = self._normalise_room_pubkey(pubkey_prefix) - if not norm: - return - with self.lock: - self._known_room_pubkeys.add(norm) - # Remove overlapping entries (different key length, same room) + norm = pubkey_prefix[:12] stale = [ k for k in self.room_login_states if k != pubkey_prefix and k[:12] == norm @@ -279,14 +240,10 @@ class SharedData: if not self.archive: return - norm = self._normalise_room_pubkey(pubkey) - if not norm: - return - + norm = pubkey[:12] archived = self.archive.get_messages_by_sender_pubkey(norm, limit) with self.lock: - self._known_room_pubkeys.add(norm) messages = [Message.from_dict(d) for d in archived] self._room_msg_cache[norm] = messages debug_print( @@ -302,9 +259,7 @@ class SharedData: Returns: List of Message objects (oldest first), or empty list. """ - norm = self._normalise_room_pubkey(pubkey) - if not norm: - return [] + norm = pubkey[:12] with self.lock: return list(self._room_msg_cache.get(norm, [])) @@ -604,8 +559,6 @@ class SharedData: k: v.copy() for k, v in self.room_login_states.items() }, - # Centrally known room keys (UI-independent) - 'known_room_pubkeys': set(self._known_room_pubkeys), # Room message cache (archived + live) 'room_messages': { k: list(v) @@ -640,28 +593,58 @@ class SharedData: return None def get_contact_name_by_prefix(self, pubkey_prefix: str) -> str: - """Resolve a pubkey/prefix to a display name. + """Resolve a pubkey/prefix to the best available display name. - Accepts either a short prefix or a longer/full public key and - returns the best-known human-readable name. This keeps room - messages readable when the room server reports the author as a - full hash instead of the shorter contact key used elsewhere. + The room server may report the author using different key fields: + a short prefix, a full public key, or a value copied into another + payload field. To keep sender display stable, match against both + the contact dict key and common pubkey-like fields stored inside + each contact record. """ if not pubkey_prefix: return "" - probe = pubkey_prefix.lower() + probe = str(pubkey_prefix).strip().lower() + if not probe: + return "" + + def _candidate_keys(contact_key: str, contact: Dict) -> List[str]: + values = [contact_key] + for field in ( + 'public_key', + 'pubkey', + 'pub_key', + 'publicKey', + 'sender_pubkey', + 'author_pubkey', + 'receiver_pubkey', + 'pubkey_prefix', + 'signature', + ): + value = contact.get(field) + if isinstance(value, str) and value.strip(): + values.append(value.strip()) + return values + with self.lock: - device_key = (self.device.public_key or '').lower() - if device_key and (device_key.startswith(probe) or probe.startswith(device_key)): + device_key = (self.device.public_key or '').strip().lower() + if device_key and ( + device_key.startswith(probe) + or probe.startswith(device_key) + ): return self.device.name or 'Me' for key, contact in self.contacts.items(): - key_lower = key.lower() - if key_lower.startswith(probe) or probe.startswith(key_lower): - name = contact.get('adv_name', '') - if name: - return name + for candidate in _candidate_keys(key, contact): + candidate_lower = candidate.lower() + if ( + candidate_lower.startswith(probe) + or probe.startswith(candidate_lower) + ): + name = str(contact.get('adv_name', '') or '').strip() + if name: + return name + return pubkey_prefix[:8] def get_contact_by_name(self, name: str) -> Optional[Tuple[str, Dict]]: