From dd60d03407a3b7bc35316e99ad6c1613bd2091b8 Mon Sep 17 00:00:00 2001 From: William Casarin Date: Fri, 30 Aug 2024 07:11:14 -0700 Subject: [PATCH] filter: create filter from contact list This adds a method for creating filters from contact lists. We will be using this for creating follow timelines. Signed-off-by: William Casarin --- src/error.rs | 4 +++ src/filter.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++++- src/timeline.rs | 8 ++++++ 3 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 116d136..e377b30 100644 --- a/src/error.rs +++ b/src/error.rs @@ -37,6 +37,7 @@ impl fmt::Display for SubscriptionError { pub enum Error { SubscriptionError(SubscriptionError), LoadFailed, + EmptyContactList, Io(io::Error), Nostr(enostr::Error), Ndb(nostrdb::Error), @@ -53,6 +54,9 @@ impl fmt::Display for Error { Self::LoadFailed => { write!(f, "load failed") } + Self::EmptyContactList => { + write!(f, "empty contact list") + } Self::Nostr(e) => write!(f, "{e}"), Self::Ndb(e) => write!(f, "{e}"), Self::Image(e) => write!(f, "{e}"), diff --git a/src/filter.rs b/src/filter.rs index c5b7889..7f48c49 100644 --- a/src/filter.rs +++ b/src/filter.rs @@ -1,5 +1,6 @@ use crate::note::NoteRef; -use nostrdb::Filter; +use crate::{Error, Result}; +use nostrdb::{Filter, FilterBuilder, Note}; pub fn should_since_optimize(limit: u64, num_notes: usize) -> bool { // rough heuristic for bailing since optimization if we don't have enough notes @@ -30,3 +31,65 @@ pub fn default_limit() -> u64 { pub fn default_remote_limit() -> u64 { 150 } + +/// Create a filter from tags. This can be used to create a filter +/// from a contact list +pub fn filter_from_tags(note: &Note) -> Result { + let mut filter = Filter::new(); + let tags = note.tags(); + let mut authors: Vec<&[u8; 32]> = Vec::with_capacity(tags.count() as usize); + let mut hashtags: Vec<&str> = vec![]; + + for tag in tags { + if tag.count() < 2 { + continue; + } + + let t = if let Some(t) = tag.get_unchecked(0).variant().str() { + t + } else { + continue; + }; + + if t == "p" { + let author = if let Some(author) = tag.get_unchecked(1).variant().id() { + author + } else { + continue; + }; + + authors.push(author); + } else if t == "t" { + let hashtag = if let Some(hashtag) = tag.get_unchecked(1).variant().str() { + hashtag + } else { + continue; + }; + + hashtags.push(hashtag); + } + } + + if authors.is_empty() && hashtags.is_empty() { + return Err(Error::EmptyContactList); + } + + // if we hit these ooms, we need to expand filter buffer size + if !authors.is_empty() { + filter.start_authors_field()?; + for author in authors { + filter.add_id_element(author)?; + } + filter.end_field(); + } + + if !hashtags.is_empty() { + filter.start_tags_field('t')?; + for hashtag in hashtags { + filter.add_str_element(hashtag)?; + } + filter.end_field(); + } + + Ok(filter) +} diff --git a/src/timeline.rs b/src/timeline.rs index bbdb264..e793c1f 100644 --- a/src/timeline.rs +++ b/src/timeline.rs @@ -1,5 +1,6 @@ use crate::app::{get_unknown_note_ids, UnknownId}; use crate::error::Error; +use crate::filter; use crate::note::NoteRef; use crate::notecache::CachedNote; use crate::{Damus, Result}; @@ -285,6 +286,13 @@ pub struct Timeline { } impl Timeline { + /// Create a timeline from a contact list + pub fn follows(contact_list: &Note) -> Result { + Ok(Timeline::new(vec![filter::filter_from_tags(contact_list)? + .kinds([1]) + .build()])) + } + pub fn new(filter: Vec) -> Self { let subscription: Option = None; let notes = TimelineTab::new(ViewFilter::Notes);