contacts: fix hashtags in filter_from_tags

split hashtag follows into a separate Filter, combining authors and
hashtags doesn't work, because this is considered an AND filter, where
we want an OR.

We may want an option to split hashtags follows into a separate column.

Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
William Casarin 2024-08-30 10:39:43 -07:00
parent 2ea6473ae2
commit a051760fd9
2 changed files with 60 additions and 21 deletions

View File

@ -1,6 +1,7 @@
use crate::note::NoteRef;
use crate::{Error, Result};
use nostrdb::{Filter, FilterBuilder, Note};
use tracing::{debug, warn};
pub fn should_since_optimize(limit: u64, num_notes: usize) -> bool {
// rough heuristic for bailing since optimization if we don't have enough notes
@ -32,13 +33,45 @@ pub fn default_remote_limit() -> u64 {
150
}
pub struct FilteredTags {
pub authors: Option<FilterBuilder>,
pub hashtags: Option<FilterBuilder>,
}
impl FilteredTags {
// TODO: make this more general
pub fn into_filter<I>(self, kinds: I) -> Vec<Filter>
where
I: IntoIterator<Item = u64> + Copy,
{
let mut filters: Vec<Filter> = Vec::with_capacity(2);
if let Some(authors) = self.authors {
filters.push(authors.kinds(kinds).build())
}
if let Some(hashtags) = self.hashtags {
filters.push(hashtags.kinds(kinds).build())
}
filters
}
}
/// 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<FilterBuilder> {
let mut filter = Filter::new();
pub fn filter_from_tags(note: &Note) -> Result<FilteredTags> {
let mut author_filter = Filter::new();
let mut hashtag_filter = Filter::new();
let mut author_res: Option<FilterBuilder> = None;
let mut hashtag_res: Option<FilterBuilder> = None;
let mut author_count = 0i32;
let mut hashtag_count = 0i32;
let tags = note.tags();
let mut authors: Vec<&[u8; 32]> = Vec::with_capacity(tags.count() as usize);
let mut hashtags: Vec<&str> = vec![];
author_filter.start_authors_field()?;
hashtag_filter.start_tags_field('t')?;
for tag in tags {
if tag.count() < 2 {
@ -58,7 +91,8 @@ pub fn filter_from_tags(note: &Note) -> Result<FilterBuilder> {
continue;
};
authors.push(author);
author_filter.add_id_element(author)?;
author_count += 1;
} else if t == "t" {
let hashtag = if let Some(hashtag) = tag.get_unchecked(1).variant().str() {
hashtag
@ -66,30 +100,35 @@ pub fn filter_from_tags(note: &Note) -> Result<FilterBuilder> {
continue;
};
hashtags.push(hashtag);
hashtag_filter.add_str_element(hashtag)?;
hashtag_count += 1;
}
}
if authors.is_empty() && hashtags.is_empty() {
author_filter.end_field();
hashtag_filter.end_field();
if author_count == 0 && hashtag_count == 0 {
warn!("no authors or hashtags found in contact list");
return Err(Error::EmptyContactList);
}
debug!(
"adding {} authors and {} hashtags to contact filter",
author_count, hashtag_count
);
// 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 author_count > 0 {
author_res = Some(author_filter)
}
if !hashtags.is_empty() {
filter.start_tags_field('t')?;
for hashtag in hashtags {
filter.add_str_element(hashtag)?;
}
filter.end_field();
if hashtag_count > 0 {
hashtag_res = Some(hashtag_filter)
}
Ok(filter)
Ok(FilteredTags {
authors: author_res,
hashtags: hashtag_res,
})
}

View File

@ -337,7 +337,7 @@ pub struct Timeline {
impl Timeline {
/// Create a timeline from a contact list
pub fn contact_list(contact_list: &Note) -> Result<Self> {
let filter = vec![filter::filter_from_tags(contact_list)?.kinds([1]).build()];
let filter = filter::filter_from_tags(contact_list)?.into_filter([1]);
let pk_src = PubkeySource::Explicit(Pubkey::new(contact_list.pubkey()));
Ok(Timeline::new(