From 384e458118939df84ac908a1816d6f960e4d0c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20D=E2=80=99Aquino?= Date: Mon, 5 Aug 2024 15:48:53 -0700 Subject: [PATCH] nostr_network_helper refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit refactors nostr_network_helper code without substantial business logic changes. It improves the code by separating concerns, making it more concise, and less repetitive Signed-off-by: Daniel D’Aquino --- .../nostr_event_extensions.rs | 32 +++- .../nostr_network_helper.rs | 175 +++++++----------- 2 files changed, 93 insertions(+), 114 deletions(-) diff --git a/src/notification_manager/nostr_event_extensions.rs b/src/notification_manager/nostr_event_extensions.rs index ad9fca9..b17ea71 100644 --- a/src/notification_manager/nostr_event_extensions.rs +++ b/src/notification_manager/nostr_event_extensions.rs @@ -1,4 +1,5 @@ -use nostr::{self, key::PublicKey, Alphabet, SingleLetterTag, TagKind::SingleLetter}; +use nostr::{self, key::PublicKey, nips::nip51::MuteList, Alphabet, SingleLetterTag, TagKind::SingleLetter}; +use nostr_sdk::{Kind, TagKind}; /// Temporary scaffolding of old methods that have not been ported to use native Event methods pub trait ExtendedEvent { @@ -13,6 +14,9 @@ pub trait ExtendedEvent { /// Retrieves a set of event IDs referenced by the note fn referenced_event_ids(&self) -> std::collections::HashSet; + + /// Retrieves a set of hashtags (t tags) referenced by the note + fn referenced_hashtags(&self) -> std::collections::HashSet; } // This is a wrapper around the Event type from strfry-policies, which adds some useful methods @@ -44,6 +48,14 @@ impl ExtendedEvent for nostr::Event { .filter_map(|tag| nostr::EventId::from_hex(tag).ok()) .collect() } + + /// Retrieves a set of hashtags (t tags) referenced by the note + fn referenced_hashtags(&self) -> std::collections::HashSet { + self.get_tags_content(SingleLetter(SingleLetterTag::lowercase(Alphabet::T))) + .iter() + .map(|tag| tag.to_string()) + .collect() + } } // MARK: - SQL String Convertible @@ -85,3 +97,21 @@ impl SqlStringConvertible for nostr::Timestamp { Ok(nostr::Timestamp::from(u64_timestamp)) } } + +pub trait MaybeConvertibleToMuteList { + fn to_mute_list(&self) -> Option; +} + +impl MaybeConvertibleToMuteList for nostr::Event { + fn to_mute_list(&self) -> Option { + if self.kind != Kind::MuteList { + return None; + } + Some(MuteList { + public_keys: self.referenced_pubkeys().iter().map(|pk| pk.clone()).collect(), + hashtags: self.referenced_hashtags().iter().map(|tag| tag.clone()).collect(), + event_ids: self.referenced_event_ids().iter().map(|id| id.clone()).collect(), + words: self.get_tags_content(TagKind::Word).iter().map(|tag| tag.to_string()).collect(), + }) + } +} diff --git a/src/notification_manager/nostr_network_helper.rs b/src/notification_manager/nostr_network_helper.rs index 69ec645..1e77c34 100644 --- a/src/notification_manager/nostr_network_helper.rs +++ b/src/notification_manager/nostr_network_helper.rs @@ -1,12 +1,17 @@ +use super::nostr_event_extensions::MaybeConvertibleToMuteList; use super::ExtendedEvent; use nostr_sdk::prelude::*; use tokio::time::{timeout, Duration}; +const NOTE_FETCH_TIMEOUT: Duration = Duration::from_secs(5); + pub struct NostrNetworkHelper { client: Client, } impl NostrNetworkHelper { + // MARK: - Initialization + pub async fn new(relay_url: String) -> Result> { let client = Client::new(&Keys::generate()); client.add_relay(relay_url.clone()).await?; @@ -14,6 +19,8 @@ impl NostrNetworkHelper { Ok(NostrNetworkHelper { client }) } + // MARK: - Answering questions about a user + pub async fn should_mute_notification_for_pubkey( &self, event: &Event, @@ -25,107 +32,40 @@ impl NostrNetworkHelper { pubkey ); if let Some(mute_list) = self.get_public_mute_list(pubkey).await { - for tag in mute_list.tags() { - match tag.kind() { - TagKind::SingleLetter(SingleLetterTag { - character: Alphabet::P, - uppercase: false, - }) => { - let tagged_pubkey: Option = - tag.content().and_then(|h| PublicKey::from_hex(h).ok()); - if let Some(tagged_pubkey) = tagged_pubkey { - if event.pubkey == tagged_pubkey { - return true; - } - } - } - TagKind::SingleLetter(SingleLetterTag { - character: Alphabet::E, - uppercase: false, - }) => { - let tagged_event_id: Option = - tag.content().and_then(|h| EventId::from_hex(h).ok()); - if let Some(tagged_event_id) = tagged_event_id { - if event.id == tagged_event_id - || event.referenced_event_ids().contains(&tagged_event_id) - { - return true; - } - } - } - TagKind::SingleLetter(SingleLetterTag { - character: Alphabet::T, - uppercase: false, - }) => { - let tagged_hashtag: Option = tag.content().map(|h| h.to_string()); - if let Some(tagged_hashtag) = tagged_hashtag { - let tags_content = - event.get_tags_content(TagKind::SingleLetter(SingleLetterTag { - character: Alphabet::T, - uppercase: false, - })); - let should_mute = tags_content.iter().any(|t| t == &tagged_hashtag); - return should_mute; - } - } - TagKind::Word => { - let tagged_word: Option = tag.content().map(|h| h.to_string()); - if let Some(tagged_word) = tagged_word { - if event - .content - .to_lowercase() - .contains(&tagged_word.to_lowercase()) - { - return true; - } - } - } - _ => {} + for muted_public_key in mute_list.public_keys { + if event.pubkey == muted_public_key { + return true; + } + } + for muted_event_id in mute_list.event_ids { + if event.id == muted_event_id + || event.referenced_event_ids().contains(&muted_event_id) + { + return true; + } + } + for muted_hashtag in mute_list.hashtags { + if event + .referenced_hashtags() + .iter() + .any(|t| t == &muted_hashtag) + { + return true; + } + } + for muted_word in mute_list.words { + if event + .content + .to_lowercase() + .contains(&muted_word.to_lowercase()) + { + return true; } } } false } - pub async fn get_public_mute_list(&self, pubkey: &PublicKey) -> Option { - let subscription_filter = Filter::new() - .kinds(vec![Kind::MuteList]) - .authors(vec![pubkey.clone()]) - .limit(1); - - let this_subscription_id = self - .client - .subscribe(Vec::from([subscription_filter]), None) - .await; - - let mut mute_list: Option = None; - let mut notifications = self.client.notifications(); - - let timeout_duration = Duration::from_secs(10); - while let Ok(result) = timeout(timeout_duration, notifications.recv()).await { - if let Ok(notification) = result { - if let RelayPoolNotification::Event { - subscription_id, - event, - .. - } = notification - { - if this_subscription_id == subscription_id && event.kind == Kind::MuteList { - mute_list = Some((*event).clone()); - break; - } - } - } - } - - if mute_list.is_none() { - log::debug!("Mute list not found for pubkey {:?}", pubkey); - } - - self.client.unsubscribe(this_subscription_id).await; - mute_list - } - pub async fn does_pubkey_follow_pubkey( &self, source_pubkey: &PublicKey, @@ -137,19 +77,29 @@ impl NostrNetworkHelper { target_pubkey ); if let Some(contact_list) = self.get_contact_list(source_pubkey).await { - let tag_contents = contact_list.get_tags_content(TagKind::SingleLetter(SingleLetterTag { - character: Alphabet::P, - uppercase: false, - })); - return tag_contents.iter().any(|t| t == &target_pubkey.to_hex()); + return contact_list.referenced_pubkeys().contains(target_pubkey); } false } - + + // MARK: - Fetching specific event types + + pub async fn get_public_mute_list(&self, pubkey: &PublicKey) -> Option { + self.fetch_single_event(pubkey, Kind::MuteList) + .await? + .to_mute_list() + } + pub async fn get_contact_list(&self, pubkey: &PublicKey) -> Option { + self.fetch_single_event(pubkey, Kind::ContactList).await + } + + // MARK: - Lower level fetching functions + + async fn fetch_single_event(&self, author: &PublicKey, kind: Kind) -> Option { let subscription_filter = Filter::new() - .kinds(vec![Kind::ContactList]) - .authors(vec![pubkey.clone()]) + .kinds(vec![kind]) + .authors(vec![author.clone()]) .limit(1); let this_subscription_id = self @@ -157,31 +107,30 @@ impl NostrNetworkHelper { .subscribe(Vec::from([subscription_filter]), None) .await; - let mut contact_list: Option = None; + let mut event: Option = None; let mut notifications = self.client.notifications(); - let timeout_duration = Duration::from_secs(10); - while let Ok(result) = timeout(timeout_duration, notifications.recv()).await { + while let Ok(result) = timeout(NOTE_FETCH_TIMEOUT, notifications.recv()).await { if let Ok(notification) = result { if let RelayPoolNotification::Event { subscription_id, - event, + event: event_option, .. } = notification { - if this_subscription_id == subscription_id && event.kind == Kind::ContactList { - contact_list = Some((*event).clone()); + if this_subscription_id == subscription_id && event_option.kind == kind { + event = Some((*event_option).clone()); break; } } } } - - if contact_list.is_none() { - log::debug!("Contact list not found for pubkey {:?}", pubkey); + + if event.is_none() { + log::info!("Event of kind {:?} not found for pubkey {:?}", kind, author); } self.client.unsubscribe(this_subscription_id).await; - contact_list + event } }