diff --git a/app/database.py b/app/database.py index a0eb447..ee827c0 100644 --- a/app/database.py +++ b/app/database.py @@ -107,7 +107,11 @@ class Database: adv_lat = COALESCE(excluded.adv_lat, contacts.adv_lat), adv_lon = COALESCE(excluded.adv_lon, contacts.adv_lon), last_seen = datetime('now'), - source = excluded.source, + source = CASE + WHEN excluded.source = 'device' THEN 'device' + WHEN contacts.source = 'device' THEN contacts.source + ELSE excluded.source + END, is_protected = CASE WHEN contacts.is_protected = 1 THEN 1 ELSE excluded.is_protected END, lastmod = datetime('now')""", fields diff --git a/app/device_manager.py b/app/device_manager.py index 30ee951..f995c32 100644 --- a/app/device_manager.py +++ b/app/device_manager.py @@ -531,8 +531,15 @@ class DeviceManager: contact = (self.mc.contacts or {}).get(pubkey, {}) name = contact.get('adv_name', contact.get('name', '')) - # If contact is unknown or has no name, firmware may have just auto-added it. - # Refresh contacts from device to pick up the new entry. + # Also check pending contacts (manual approval mode) + if not name: + pending = (self.mc.pending_contacts or {}).get(pubkey, {}) + if pending: + name = pending.get('adv_name', pending.get('name', '')) + if not contact: + contact = pending + + # If contact is still unknown, firmware may have just auto-added it. if not name and pubkey not in (self.mc.contacts or {}): logger.info(f"Unknown advert from {pubkey[:8]}..., refreshing contacts") await self.mc.ensure_contacts(follow=True) @@ -749,9 +756,26 @@ class DeviceManager: return if self._is_manual_approval_enabled(): - # Manual mode: don't add to DB, just notify frontend - # meshcore library already puts it in mc.pending_contacts + # Manual mode: meshcore puts it in mc.pending_contacts for approval logger.info(f"Pending contact (manual mode): {name} ({pubkey[:8]}...)") + + # Also add to DB cache for @mentions and Cache filter + last_adv = data.get('last_advert') + last_advert_val = ( + str(int(last_adv)) + if last_adv and isinstance(last_adv, (int, float)) and last_adv > 0 + else str(int(time.time())) + ) + self.db.upsert_contact( + public_key=pubkey, + name=name, + type=data.get('type', data.get('adv_type', 0)), + adv_lat=data.get('adv_lat'), + adv_lon=data.get('adv_lon'), + last_advert=last_advert_val, + source='advert', # cache-only until approved + ) + if self.socketio: self.socketio.emit('pending_contact', { 'public_key': pubkey, @@ -1158,6 +1182,7 @@ class DeviceManager: 'type': c.get('type', c.get('adv_type', 0)), 'adv_lat': c.get('adv_lat'), 'adv_lon': c.get('adv_lon'), + 'last_advert': c.get('last_advert'), } for pk, c in pending.items() ] diff --git a/app/routes/api.py b/app/routes/api.py index e0213e6..6510a60 100644 --- a/app/routes/api.py +++ b/app/routes/api.py @@ -675,12 +675,20 @@ def get_cached_contacts(): contacts = [] for c in db_contacts: pk = c.get('public_key', '') + # Parse last_advert to numeric timestamp + la = c.get('last_advert') + try: + last_advert_ts = int(la) if la else 0 + except (ValueError, TypeError): + last_advert_ts = 0 + contacts.append({ 'public_key': pk, 'public_key_prefix': pk[:12], 'name': c.get('name', ''), 'first_seen': c.get('first_seen', ''), 'last_seen': c.get('last_seen', ''), + 'last_advert': last_advert_ts, 'source': c.get('source', ''), 'adv_lat': c.get('adv_lat'), 'adv_lon': c.get('adv_lon'), diff --git a/app/static/js/contacts.js b/app/static/js/contacts.js index 47065bd..66dd792 100644 --- a/app/static/js/contacts.js +++ b/app/static/js/contacts.js @@ -1652,9 +1652,9 @@ async function loadExistingContacts() { public_key: c.public_key, public_key_prefix: c.public_key_prefix || c.public_key.substring(0, 12), type_label: c.type_label || '', - adv_lat: c.lat || 0, - adv_lon: c.lon || 0, - last_seen: c.last_seen || 0, + adv_lat: c.adv_lat || 0, + adv_lon: c.adv_lon || 0, + last_seen: c.last_advert || 0, on_device: false, source: c.source || 'cache' }));