@@ -35,6 +35,10 @@ impl PubkeySource {
|
||||
}
|
||||
|
||||
impl ListKind {
|
||||
pub fn contact_list(pk_src: PubkeySource) -> Self {
|
||||
ListKind::Contact(pk_src)
|
||||
}
|
||||
|
||||
pub fn pubkey_source(&self) -> Option<&PubkeySource> {
|
||||
match self {
|
||||
ListKind::Contact(pk_src) => Some(pk_src),
|
||||
@@ -54,6 +58,9 @@ impl ListKind {
|
||||
pub enum TimelineKind {
|
||||
List(ListKind),
|
||||
|
||||
/// The last not per pubkey
|
||||
Algo(AlgoTimeline),
|
||||
|
||||
Notifications(PubkeySource),
|
||||
|
||||
Profile(PubkeySource),
|
||||
@@ -69,10 +76,19 @@ pub enum TimelineKind {
|
||||
Hashtag(String),
|
||||
}
|
||||
|
||||
/// Hardcoded algo timelines
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AlgoTimeline {
|
||||
/// LastPerPubkey: a special nostr query that fetches the last N
|
||||
/// notes for each pubkey on the list
|
||||
LastPerPubkey(ListKind),
|
||||
}
|
||||
|
||||
impl Display for TimelineKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
TimelineKind::List(ListKind::Contact(_src)) => f.write_str("Contacts"),
|
||||
TimelineKind::Algo(AlgoTimeline::LastPerPubkey(_lk)) => f.write_str("Last Notes"),
|
||||
TimelineKind::Generic => f.write_str("Timeline"),
|
||||
TimelineKind::Notifications(_) => f.write_str("Notifications"),
|
||||
TimelineKind::Profile(_) => f.write_str("Profile"),
|
||||
@@ -87,6 +103,7 @@ impl TimelineKind {
|
||||
pub fn pubkey_source(&self) -> Option<&PubkeySource> {
|
||||
match self {
|
||||
TimelineKind::List(list_kind) => list_kind.pubkey_source(),
|
||||
TimelineKind::Algo(AlgoTimeline::LastPerPubkey(list_kind)) => list_kind.pubkey_source(),
|
||||
TimelineKind::Notifications(pk_src) => Some(pk_src),
|
||||
TimelineKind::Profile(pk_src) => Some(pk_src),
|
||||
TimelineKind::Universe => None,
|
||||
@@ -96,8 +113,27 @@ impl TimelineKind {
|
||||
}
|
||||
}
|
||||
|
||||
/// Some feeds are not realtime, like certain algo feeds
|
||||
pub fn should_subscribe_locally(&self) -> bool {
|
||||
match self {
|
||||
TimelineKind::Algo(AlgoTimeline::LastPerPubkey(_list_kind)) => false,
|
||||
|
||||
TimelineKind::List(_list_kind) => true,
|
||||
TimelineKind::Notifications(_pk_src) => true,
|
||||
TimelineKind::Profile(_pk_src) => true,
|
||||
TimelineKind::Universe => true,
|
||||
TimelineKind::Generic => true,
|
||||
TimelineKind::Hashtag(_ht) => true,
|
||||
TimelineKind::Thread(_ht) => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn last_per_pubkey(list_kind: ListKind) -> Self {
|
||||
TimelineKind::Algo(AlgoTimeline::LastPerPubkey(list_kind))
|
||||
}
|
||||
|
||||
pub fn contact_list(pk: PubkeySource) -> Self {
|
||||
TimelineKind::List(ListKind::Contact(pk))
|
||||
TimelineKind::List(ListKind::contact_list(pk))
|
||||
}
|
||||
|
||||
pub fn is_contacts(&self) -> bool {
|
||||
@@ -138,6 +174,48 @@ impl TimelineKind {
|
||||
None
|
||||
}
|
||||
|
||||
TimelineKind::Algo(AlgoTimeline::LastPerPubkey(ListKind::Contact(pk_src))) => {
|
||||
let pk = match &pk_src {
|
||||
PubkeySource::DeckAuthor => default_user?,
|
||||
PubkeySource::Explicit(pk) => pk.bytes(),
|
||||
};
|
||||
|
||||
let contact_filter = Filter::new().authors([pk]).kinds([3]).limit(1).build();
|
||||
|
||||
let txn = Transaction::new(ndb).expect("txn");
|
||||
let results = ndb
|
||||
.query(&txn, &[contact_filter.clone()], 1)
|
||||
.expect("contact query failed?");
|
||||
|
||||
let kind_fn = TimelineKind::last_per_pubkey;
|
||||
let tabs = TimelineTab::only_notes_and_replies();
|
||||
|
||||
if results.is_empty() {
|
||||
return Some(Timeline::new(
|
||||
kind_fn(ListKind::contact_list(pk_src)),
|
||||
FilterState::needs_remote(vec![contact_filter.clone()]),
|
||||
tabs,
|
||||
));
|
||||
}
|
||||
|
||||
let list_kind = ListKind::contact_list(pk_src);
|
||||
|
||||
match Timeline::last_per_pubkey(&results[0].note, &list_kind) {
|
||||
Err(Error::App(notedeck::Error::Filter(FilterError::EmptyContactList))) => {
|
||||
Some(Timeline::new(
|
||||
kind_fn(list_kind),
|
||||
FilterState::needs_remote(vec![contact_filter]),
|
||||
tabs,
|
||||
))
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Unexpected error: {e}");
|
||||
None
|
||||
}
|
||||
Ok(tl) => Some(tl),
|
||||
}
|
||||
}
|
||||
|
||||
TimelineKind::Profile(pk_src) => {
|
||||
let pk = match &pk_src {
|
||||
PubkeySource::DeckAuthor => default_user?,
|
||||
@@ -222,6 +300,9 @@ impl TimelineKind {
|
||||
TimelineKind::List(list_kind) => match list_kind {
|
||||
ListKind::Contact(_pubkey_source) => ColumnTitle::simple("Contacts"),
|
||||
},
|
||||
TimelineKind::Algo(AlgoTimeline::LastPerPubkey(list_kind)) => match list_kind {
|
||||
ListKind::Contact(_pubkey_source) => ColumnTitle::simple("Contacts (last notes)"),
|
||||
},
|
||||
TimelineKind::Notifications(_pubkey_source) => ColumnTitle::simple("Notifications"),
|
||||
TimelineKind::Profile(_pubkey_source) => ColumnTitle::needs_db(self),
|
||||
TimelineKind::Thread(_root_id) => ColumnTitle::simple("Thread"),
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::{
|
||||
error::Error,
|
||||
subscriptions::{self, SubKind, Subscriptions},
|
||||
thread::Thread,
|
||||
timeline::kind::ListKind,
|
||||
Result,
|
||||
};
|
||||
|
||||
@@ -29,7 +30,7 @@ pub mod kind;
|
||||
pub mod route;
|
||||
|
||||
pub use cache::{TimelineCache, TimelineCacheKey};
|
||||
pub use kind::{ColumnTitle, PubkeySource, TimelineKind};
|
||||
pub use kind::{AlgoTimeline, ColumnTitle, PubkeySource, TimelineKind};
|
||||
pub use route::TimelineRoute;
|
||||
|
||||
#[derive(Debug, Hash, Copy, Clone, Eq, PartialEq)]
|
||||
@@ -227,6 +228,18 @@ impl Timeline {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn last_per_pubkey(list: &Note, list_kind: &ListKind) -> Result<Self> {
|
||||
let kind = 1;
|
||||
let notes_per_pk = 1;
|
||||
let filter = filter::last_n_per_pubkey_from_tags(list, kind, notes_per_pk)?;
|
||||
|
||||
Ok(Timeline::new(
|
||||
TimelineKind::last_per_pubkey(list_kind.clone()),
|
||||
FilterState::ready(filter),
|
||||
TimelineTab::only_notes_and_replies(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn hashtag(hashtag: String) -> Self {
|
||||
let filter = Filter::new()
|
||||
.kinds([1])
|
||||
@@ -397,6 +410,11 @@ impl Timeline {
|
||||
note_cache: &mut NoteCache,
|
||||
reversed: bool,
|
||||
) -> Result<()> {
|
||||
if !self.kind.should_subscribe_locally() {
|
||||
// don't need to poll for timelines that don't have local subscriptions
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let sub = self
|
||||
.subscription
|
||||
.ok_or(Error::App(notedeck::Error::no_active_sub()))?;
|
||||
@@ -601,13 +619,20 @@ fn setup_initial_timeline(
|
||||
note_cache: &mut NoteCache,
|
||||
filters: &[Filter],
|
||||
) -> Result<()> {
|
||||
timeline.subscription = Some(ndb.subscribe(filters)?);
|
||||
// some timelines are one-shot and a refreshed, like last_per_pubkey algo feed
|
||||
if timeline.kind.should_subscribe_locally() {
|
||||
timeline.subscription = Some(ndb.subscribe(filters)?);
|
||||
}
|
||||
let txn = Transaction::new(ndb)?;
|
||||
debug!(
|
||||
"querying nostrdb sub {:?} {:?}",
|
||||
timeline.subscription, timeline.filter
|
||||
);
|
||||
let lim = filters[0].limit().unwrap_or(filter::default_limit()) as i32;
|
||||
|
||||
let mut lim = 0i32;
|
||||
for filter in filters {
|
||||
lim += filter.limit().unwrap_or(1) as i32;
|
||||
}
|
||||
|
||||
let notes: Vec<NoteRef> = ndb
|
||||
.query(&txn, filters, lim)?
|
||||
|
||||
Reference in New Issue
Block a user