diff --git a/mutiny-core/src/nostr/mod.rs b/mutiny-core/src/nostr/mod.rs index 304567026..54efa2c3b 100644 --- a/mutiny-core/src/nostr/mod.rs +++ b/mutiny-core/src/nostr/mod.rs @@ -234,9 +234,15 @@ impl NostrManager { let event = self.storage.get_data::(NOSTR_CONTACT_LIST)?; // listen for latest contact list events - let time_stamp = match event.map(|e| e.created_at) { + let time_stamp = match event { None => Timestamp::from(0), - Some(time) => time + 1_i64, // add one so we get only new events + Some(event) => { + if event.pubkey == self.public_key { + event.created_at + 1_i64 // add one so we get only new events + } else { + Timestamp::from(0) + } + } }; Ok(Filter::new() @@ -262,7 +268,23 @@ impl NostrManager { lnurl: Option, nip05: Option, ) -> Result { - let current = self.get_profile()?; + // pull latest profile from primal + let current = match self.primal_client.get_user_profile(self.public_key).await { + Ok(Some(meta)) => meta, + Ok(None) => { + log_warn!(self.logger, "No profile found for user, creating new"); + Metadata::default() + } + Err(e) => { + // if we can't get the profile from primal, fall back to local + // otherwise we can't create/edit profile if the primal server is down + log_error!( + self.logger, + "Failed to get user profile from primal, falling back to local: {e}" + ); + self.storage.get_nostr_profile()?.unwrap_or_default() + } + }; let with_name = if let Some(name) = name { current.name(name) @@ -309,16 +331,35 @@ impl NostrManager { let builder = match event { Some(event) => { + // if event is for a different key, we need to pull down latest + let (mut tags, content) = if event.pubkey != self.public_key { + let (opt, _) = self + .primal_client + .get_nostr_contacts(self.public_key) + .await?; + + // if key doesn't have a contact list, create a new one + match opt { + None => (vec![], String::new()), + Some(event) => { + let tags = event.tags.clone(); + let content = event.content.clone(); + (tags, content) + } + } + } else { + (event.tags.clone(), event.content.clone()) + }; + // check if we're already following - if event.iter_tags().any(|tag| { + if tags.iter().any(|tag| { matches!(tag, Tag::PublicKey { public_key, uppercase: false, .. } if *public_key == npub) }) { return Ok(()); } - let mut tags = event.tags.clone(); tags.push(Tag::public_key(npub)); - EventBuilder::new(Kind::ContactList, &event.content, tags) + EventBuilder::new(Kind::ContactList, content, tags) } None => EventBuilder::new(Kind::ContactList, "", [Tag::public_key(npub)]), }; @@ -346,7 +387,26 @@ impl NostrManager { match event { None => Ok(()), // no follow list, nothing to unfollow Some(event) => { - let mut tags = event.tags.clone(); + // if event is for a different key, we need to pull down latest + let (mut tags, content) = if event.pubkey != self.public_key { + let (opt, _) = self + .primal_client + .get_nostr_contacts(self.public_key) + .await?; + + // if key doesn't have a contact list, create a new one + match opt { + None => (vec![], String::new()), + Some(event) => { + let tags = event.tags.clone(); + let content = event.content.clone(); + (tags, content) + } + } + } else { + (event.tags.clone(), event.content.clone()) + }; + tags.retain(|tag| { !matches!(tag, Tag::PublicKey { public_key, uppercase: false, .. } if *public_key == npub) }); @@ -357,7 +417,7 @@ impl NostrManager { return Ok(()); } - let builder = EventBuilder::new(Kind::ContactList, &event.content, tags); + let builder = EventBuilder::new(Kind::ContactList, content, tags); let event = self.primary_key.sign_event_builder(builder).await?; let event_id = self.client.send_event(event.clone()).await?; diff --git a/mutiny-core/src/storage.rs b/mutiny-core/src/storage.rs index 6abdd3a80..388769907 100644 --- a/mutiny-core/src/storage.rs +++ b/mutiny-core/src/storage.rs @@ -989,10 +989,16 @@ pub(crate) fn update_nostr_contact_list( return Err(MutinyError::InvalidArgumentsError); } - // if the event is newer than the one in storage, update it + // if the event is for the same key and is older than the one in storage + // if it is a different key, always save it, that means the user has imported an nsec // otherwise ignore it match storage.get_data::(NOSTR_CONTACT_LIST) { - Ok(Some(event)) if event.created_at > contact_list_event.created_at => Ok(false), + Ok(Some(event)) + if event.created_at > contact_list_event.created_at + && contact_list_event.pubkey == event.pubkey => + { + Ok(false) + } _ => storage .set_data(NOSTR_CONTACT_LIST.to_string(), contact_list_event, None) .map(|_| true),