filter.rs (5581B)
1 use crate::error::{Error, FilterError}; 2 use crate::note::NoteRef; 3 use crate::Result; 4 use nostrdb::{Filter, FilterBuilder, Note, Subscription}; 5 use tracing::{debug, warn}; 6 7 /// A unified subscription has a local and remote component. The remote subid 8 /// tracks data received remotely, and local 9 #[derive(Debug, Clone)] 10 pub struct UnifiedSubscription { 11 pub local: Subscription, 12 pub remote: String, 13 } 14 15 /// We may need to fetch some data from relays before our filter is ready. 16 /// [`FilterState`] tracks this. 17 #[derive(Debug, Clone)] 18 pub enum FilterState { 19 NeedsRemote(Vec<Filter>), 20 FetchingRemote(UnifiedSubscription), 21 GotRemote(Subscription), 22 Ready(Vec<Filter>), 23 Broken(FilterError), 24 } 25 26 impl FilterState { 27 /// We tried to fetch a filter but we wither got no data or the data 28 /// was corrupted, preventing us from getting to the Ready state. 29 /// Just mark the timeline as broken so that we can signal to the 30 /// user that something went wrong 31 pub fn broken(reason: FilterError) -> Self { 32 Self::Broken(reason) 33 } 34 35 /// The filter is ready 36 pub fn ready(filter: Vec<Filter>) -> Self { 37 Self::Ready(filter) 38 } 39 40 /// We need some data from relays before we can continue. Example: 41 /// for home timelines where we don't have a contact list yet. We 42 /// need to fetch the contact list before we have the right timeline 43 /// filter. 44 pub fn needs_remote(filter: Vec<Filter>) -> Self { 45 Self::NeedsRemote(filter) 46 } 47 48 /// We got the remote data. Local data should be available to build 49 /// the filter for the [`FilterState::Ready`] state 50 pub fn got_remote(local_sub: Subscription) -> Self { 51 Self::GotRemote(local_sub) 52 } 53 54 /// We have sent off a remote subscription to get data needed for the 55 /// filter. The string is the subscription id 56 pub fn fetching_remote(sub_id: String, local_sub: Subscription) -> Self { 57 let unified_sub = UnifiedSubscription { 58 local: local_sub, 59 remote: sub_id, 60 }; 61 Self::FetchingRemote(unified_sub) 62 } 63 } 64 65 pub fn should_since_optimize(limit: u64, num_notes: usize) -> bool { 66 // rough heuristic for bailing since optimization if we don't have enough notes 67 limit as usize <= num_notes 68 } 69 70 pub fn since_optimize_filter_with(filter: Filter, notes: &[NoteRef], since_gap: u64) -> Filter { 71 // Get the latest entry in the events 72 if notes.is_empty() { 73 return filter; 74 } 75 76 // get the latest note 77 let latest = notes[0]; 78 let since = latest.created_at - since_gap; 79 80 filter.since_mut(since) 81 } 82 83 pub fn since_optimize_filter(filter: Filter, notes: &[NoteRef]) -> Filter { 84 since_optimize_filter_with(filter, notes, 60) 85 } 86 87 pub fn default_limit() -> u64 { 88 500 89 } 90 91 pub fn default_remote_limit() -> u64 { 92 250 93 } 94 95 pub struct FilteredTags { 96 pub authors: Option<FilterBuilder>, 97 pub hashtags: Option<FilterBuilder>, 98 } 99 100 impl FilteredTags { 101 pub fn into_follow_filter(self) -> Vec<Filter> { 102 self.into_filter([1], default_limit()) 103 } 104 105 // TODO: make this more general 106 pub fn into_filter<I>(self, kinds: I, limit: u64) -> Vec<Filter> 107 where 108 I: IntoIterator<Item = u64> + Copy, 109 { 110 let mut filters: Vec<Filter> = Vec::with_capacity(2); 111 112 if let Some(authors) = self.authors { 113 filters.push(authors.kinds(kinds).limit(limit).build()) 114 } 115 116 if let Some(hashtags) = self.hashtags { 117 filters.push(hashtags.kinds(kinds).limit(limit).build()) 118 } 119 120 filters 121 } 122 } 123 124 /// Create a filter from tags. This can be used to create a filter 125 /// from a contact list 126 pub fn filter_from_tags(note: &Note) -> Result<FilteredTags> { 127 let mut author_filter = Filter::new(); 128 let mut hashtag_filter = Filter::new(); 129 let mut author_res: Option<FilterBuilder> = None; 130 let mut hashtag_res: Option<FilterBuilder> = None; 131 let mut author_count = 0i32; 132 let mut hashtag_count = 0i32; 133 134 let tags = note.tags(); 135 136 author_filter.start_authors_field()?; 137 hashtag_filter.start_tags_field('t')?; 138 139 for tag in tags { 140 if tag.count() < 2 { 141 continue; 142 } 143 144 let t = if let Some(t) = tag.get_unchecked(0).variant().str() { 145 t 146 } else { 147 continue; 148 }; 149 150 if t == "p" { 151 let author = if let Some(author) = tag.get_unchecked(1).variant().id() { 152 author 153 } else { 154 continue; 155 }; 156 157 author_filter.add_id_element(author)?; 158 author_count += 1; 159 } else if t == "t" { 160 let hashtag = if let Some(hashtag) = tag.get_unchecked(1).variant().str() { 161 hashtag 162 } else { 163 continue; 164 }; 165 166 hashtag_filter.add_str_element(hashtag)?; 167 hashtag_count += 1; 168 } 169 } 170 171 author_filter.end_field(); 172 hashtag_filter.end_field(); 173 174 if author_count == 0 && hashtag_count == 0 { 175 warn!("no authors or hashtags found in contact list"); 176 return Err(Error::empty_contact_list()); 177 } 178 179 debug!( 180 "adding {} authors and {} hashtags to contact filter", 181 author_count, hashtag_count 182 ); 183 184 // if we hit these ooms, we need to expand filter buffer size 185 if author_count > 0 { 186 author_res = Some(author_filter) 187 } 188 189 if hashtag_count > 0 { 190 hashtag_res = Some(hashtag_filter) 191 } 192 193 Ok(FilteredTags { 194 authors: author_res, 195 hashtags: hashtag_res, 196 }) 197 }