nostr_network_helper refactor

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 <daniel@daquino.me>
This commit is contained in:
Daniel D’Aquino
2024-08-05 15:48:53 -07:00
parent 3ca3a83257
commit 384e458118
2 changed files with 93 additions and 114 deletions

View File

@ -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 /// Temporary scaffolding of old methods that have not been ported to use native Event methods
pub trait ExtendedEvent { pub trait ExtendedEvent {
@ -13,6 +14,9 @@ pub trait ExtendedEvent {
/// Retrieves a set of event IDs referenced by the note /// Retrieves a set of event IDs referenced by the note
fn referenced_event_ids(&self) -> std::collections::HashSet<nostr::EventId>; fn referenced_event_ids(&self) -> std::collections::HashSet<nostr::EventId>;
/// Retrieves a set of hashtags (t tags) referenced by the note
fn referenced_hashtags(&self) -> std::collections::HashSet<String>;
} }
// This is a wrapper around the Event type from strfry-policies, which adds some useful methods // 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()) .filter_map(|tag| nostr::EventId::from_hex(tag).ok())
.collect() .collect()
} }
/// Retrieves a set of hashtags (t tags) referenced by the note
fn referenced_hashtags(&self) -> std::collections::HashSet<String> {
self.get_tags_content(SingleLetter(SingleLetterTag::lowercase(Alphabet::T)))
.iter()
.map(|tag| tag.to_string())
.collect()
}
} }
// MARK: - SQL String Convertible // MARK: - SQL String Convertible
@ -85,3 +97,21 @@ impl SqlStringConvertible for nostr::Timestamp {
Ok(nostr::Timestamp::from(u64_timestamp)) Ok(nostr::Timestamp::from(u64_timestamp))
} }
} }
pub trait MaybeConvertibleToMuteList {
fn to_mute_list(&self) -> Option<MuteList>;
}
impl MaybeConvertibleToMuteList for nostr::Event {
fn to_mute_list(&self) -> Option<MuteList> {
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(),
})
}
}

View File

@ -1,12 +1,17 @@
use super::nostr_event_extensions::MaybeConvertibleToMuteList;
use super::ExtendedEvent; use super::ExtendedEvent;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use tokio::time::{timeout, Duration}; use tokio::time::{timeout, Duration};
const NOTE_FETCH_TIMEOUT: Duration = Duration::from_secs(5);
pub struct NostrNetworkHelper { pub struct NostrNetworkHelper {
client: Client, client: Client,
} }
impl NostrNetworkHelper { impl NostrNetworkHelper {
// MARK: - Initialization
pub async fn new(relay_url: String) -> Result<Self, Box<dyn std::error::Error>> { pub async fn new(relay_url: String) -> Result<Self, Box<dyn std::error::Error>> {
let client = Client::new(&Keys::generate()); let client = Client::new(&Keys::generate());
client.add_relay(relay_url.clone()).await?; client.add_relay(relay_url.clone()).await?;
@ -14,6 +19,8 @@ impl NostrNetworkHelper {
Ok(NostrNetworkHelper { client }) Ok(NostrNetworkHelper { client })
} }
// MARK: - Answering questions about a user
pub async fn should_mute_notification_for_pubkey( pub async fn should_mute_notification_for_pubkey(
&self, &self,
event: &Event, event: &Event,
@ -25,107 +32,40 @@ impl NostrNetworkHelper {
pubkey pubkey
); );
if let Some(mute_list) = self.get_public_mute_list(pubkey).await { if let Some(mute_list) = self.get_public_mute_list(pubkey).await {
for tag in mute_list.tags() { for muted_public_key in mute_list.public_keys {
match tag.kind() { if event.pubkey == muted_public_key {
TagKind::SingleLetter(SingleLetterTag {
character: Alphabet::P,
uppercase: false,
}) => {
let tagged_pubkey: Option<PublicKey> =
tag.content().and_then(|h| PublicKey::from_hex(h).ok());
if let Some(tagged_pubkey) = tagged_pubkey {
if event.pubkey == tagged_pubkey {
return true; return true;
} }
} }
} for muted_event_id in mute_list.event_ids {
TagKind::SingleLetter(SingleLetterTag { if event.id == muted_event_id
character: Alphabet::E, || event.referenced_event_ids().contains(&muted_event_id)
uppercase: false,
}) => {
let tagged_event_id: Option<EventId> =
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; return true;
} }
} }
} for muted_hashtag in mute_list.hashtags {
TagKind::SingleLetter(SingleLetterTag { if event
character: Alphabet::T, .referenced_hashtags()
uppercase: false, .iter()
}) => { .any(|t| t == &muted_hashtag)
let tagged_hashtag: Option<String> = tag.content().map(|h| h.to_string()); {
if let Some(tagged_hashtag) = tagged_hashtag { return true;
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 => { for muted_word in mute_list.words {
let tagged_word: Option<String> = tag.content().map(|h| h.to_string());
if let Some(tagged_word) = tagged_word {
if event if event
.content .content
.to_lowercase() .to_lowercase()
.contains(&tagged_word.to_lowercase()) .contains(&muted_word.to_lowercase())
{ {
return true; return true;
} }
} }
} }
_ => {}
}
}
}
false false
} }
pub async fn get_public_mute_list(&self, pubkey: &PublicKey) -> Option<Event> {
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<Event> = 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( pub async fn does_pubkey_follow_pubkey(
&self, &self,
source_pubkey: &PublicKey, source_pubkey: &PublicKey,
@ -137,19 +77,29 @@ impl NostrNetworkHelper {
target_pubkey target_pubkey
); );
if let Some(contact_list) = self.get_contact_list(source_pubkey).await { if let Some(contact_list) = self.get_contact_list(source_pubkey).await {
let tag_contents = contact_list.get_tags_content(TagKind::SingleLetter(SingleLetterTag { return contact_list.referenced_pubkeys().contains(target_pubkey);
character: Alphabet::P,
uppercase: false,
}));
return tag_contents.iter().any(|t| t == &target_pubkey.to_hex());
} }
false false
} }
// MARK: - Fetching specific event types
pub async fn get_public_mute_list(&self, pubkey: &PublicKey) -> Option<MuteList> {
self.fetch_single_event(pubkey, Kind::MuteList)
.await?
.to_mute_list()
}
pub async fn get_contact_list(&self, pubkey: &PublicKey) -> Option<Event> { pub async fn get_contact_list(&self, pubkey: &PublicKey) -> Option<Event> {
self.fetch_single_event(pubkey, Kind::ContactList).await
}
// MARK: - Lower level fetching functions
async fn fetch_single_event(&self, author: &PublicKey, kind: Kind) -> Option<Event> {
let subscription_filter = Filter::new() let subscription_filter = Filter::new()
.kinds(vec![Kind::ContactList]) .kinds(vec![kind])
.authors(vec![pubkey.clone()]) .authors(vec![author.clone()])
.limit(1); .limit(1);
let this_subscription_id = self let this_subscription_id = self
@ -157,31 +107,30 @@ impl NostrNetworkHelper {
.subscribe(Vec::from([subscription_filter]), None) .subscribe(Vec::from([subscription_filter]), None)
.await; .await;
let mut contact_list: Option<Event> = None; let mut event: Option<Event> = None;
let mut notifications = self.client.notifications(); let mut notifications = self.client.notifications();
let timeout_duration = Duration::from_secs(10); while let Ok(result) = timeout(NOTE_FETCH_TIMEOUT, notifications.recv()).await {
while let Ok(result) = timeout(timeout_duration, notifications.recv()).await {
if let Ok(notification) = result { if let Ok(notification) = result {
if let RelayPoolNotification::Event { if let RelayPoolNotification::Event {
subscription_id, subscription_id,
event, event: event_option,
.. ..
} = notification } = notification
{ {
if this_subscription_id == subscription_id && event.kind == Kind::ContactList { if this_subscription_id == subscription_id && event_option.kind == kind {
contact_list = Some((*event).clone()); event = Some((*event_option).clone());
break; break;
} }
} }
} }
} }
if contact_list.is_none() { if event.is_none() {
log::debug!("Contact list not found for pubkey {:?}", pubkey); log::info!("Event of kind {:?} not found for pubkey {:?}", kind, author);
} }
self.client.unsubscribe(this_subscription_id).await; self.client.unsubscribe(this_subscription_id).await;
contact_list event
} }
} }