diff --git a/Cargo.lock b/Cargo.lock index 0a902d54..f700e8fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2430,7 +2430,7 @@ dependencies = [ [[package]] name = "nostr-types" version = "0.5.0-unstable" -source = "git+https://github.com/mikedilger/nostr-types#a9bb464cb5dfe93b3921ef97249725c415845b00" +source = "git+https://github.com/mikedilger/nostr-types#7ac6a3fb8692f56976ecff1b38a405cb2ef8b820" dependencies = [ "aes", "base64 0.21.0", diff --git a/src/db/event.rs b/src/db/event.rs index 0ba8429b..aef0c9db 100644 --- a/src/db/event.rs +++ b/src/db/event.rs @@ -104,23 +104,15 @@ impl DbEvent { Some(pk) => pk.into(), }; - let mut kinds = vec![EventKind::TextNote, EventKind::EventDeletion]; - if GLOBALS.settings.read().direct_messages { - kinds.push(EventKind::EncryptedDirectMessage); - } - if GLOBALS.settings.read().reposts { - kinds.push(EventKind::Repost); - } - if GLOBALS.settings.read().reactions { - kinds.push(EventKind::Reaction); - } - - let kinds: Vec = kinds + let kinds: String = GLOBALS + .settings + .read() + .feed_related_event_kinds() .iter() .map(|e| >::into(*e)) .map(|e| e.to_string()) - .collect(); - let kinds = kinds.join(","); + .collect::>() + .join(","); let sql = format!( "SELECT id, raw, pubkey, created_at, kind, content, ots FROM event \ @@ -228,21 +220,96 @@ impl DbEvent { Ok(()) } - /* - pub async fn delete(criteria: &str) -> Result<(), Error> { - let sql = format!("DELETE FROM event WHERE {}", criteria); - + // This is for replaceable (not parameterized!) events only. + // Returns true if it inserted something, false if it didn't have to. + pub async fn replace(event: DbEvent) -> Result { + // Delete anything older + let sql = "DELETE FROM event WHERE pubkey=? and kind=? and created_at(()) }) .await??; - Ok(()) + // Check if anything remains (which must then be newer) + let sql = "SELECT count(*) FROM event WHERE pubkey=? and kind=?".to_owned(); + let pubkey: String = event.pubkey.as_str().to_owned(); + let kind: u64 = event.kind; + let count: usize = spawn_blocking(move || { + let maybe_db = GLOBALS.db.blocking_lock(); + let db = maybe_db.as_ref().unwrap(); + let mut stmt = db.prepare(&sql)?; + stmt.raw_bind_parameter(1, &pubkey)?; + stmt.raw_bind_parameter(2, kind)?; + let mut rows = stmt.raw_query(); + if let Some(row) = rows.next()? { + return Ok(row.get(0)?); + } + Ok::(0) + }) + .await??; + + // If nothing is newer, save this event + if count == 0 { + Self::insert(event).await?; + Ok(true) + } else { + Ok(false) + } + } + + // Returns true if it inserted something, false if it didn't have to. + pub async fn replace_parameterized(event: DbEvent, parameter: String) -> Result { + // Delete anything older + let sql = "DELETE FROM event WHERE pubkey=? and kind=? and created_at(()) + }) + .await??; + + // Check if anything remains (which must then be newer) + let sql = "SELECT count(*) FROM event WHERE pubkey=? and kind=? AND id IN (SELECT event FROM event_tag WHERE event=? AND label='d' AND field0=?)".to_owned(); + let pubkey: String = event.pubkey.as_str().to_owned(); + let kind: u64 = event.kind; + let id: String = event.id.as_str().to_owned(); + let param: String = parameter.clone(); + let count: usize = spawn_blocking(move || { + let maybe_db = GLOBALS.db.blocking_lock(); + let db = maybe_db.as_ref().unwrap(); + let mut stmt = db.prepare(&sql)?; + stmt.raw_bind_parameter(1, &pubkey)?; + stmt.raw_bind_parameter(2, kind)?; + stmt.raw_bind_parameter(3, &id)?; + stmt.raw_bind_parameter(4, ¶m)?; + let mut rows = stmt.raw_query(); + if let Some(row) = rows.next()? { + return Ok(row.get(0)?); + } + Ok::(0) + }) + .await??; + + // If nothing is newer, save this event + if count == 0 { + Self::insert(event).await?; + Ok(true) + } else { + Ok(false) + } } - */ /* pub async fn get_author(id: IdHex) -> Result, Error> { diff --git a/src/feed.rs b/src/feed.rs index ec9eef2b..235d8101 100644 --- a/src/feed.rs +++ b/src/feed.rs @@ -207,14 +207,9 @@ impl Feed { *self.last_computed.write() = Some(Instant::now()); // Copy some values from settings - let (feed_recompute_interval_ms, reposts, long_form) = { - let settings = GLOBALS.settings.read(); - ( - settings.feed_recompute_interval_ms, - settings.reposts, - settings.show_long_form, - ) - }; + let feed_recompute_interval_ms = GLOBALS.settings.read().feed_recompute_interval_ms; + + let kinds = GLOBALS.settings.read().feed_related_event_kinds(); // We only need to set this the first time, but has to be after // settings is loaded (can't be in new()). Doing it every time is @@ -238,12 +233,9 @@ impl Feed { .iter() .map(|r| r.value().to_owned()) .filter(|e| e.created_at <= now) // no future events - .filter(|e| { - // feed related - e.kind == EventKind::TextNote - || (reposts && (e.kind == EventKind::Repost)) - || (long_form && (e.kind == EventKind::LongFormContent)) - }) + .filter(|e| kinds.contains(&e.kind)) // feed related + .filter(|e| e.kind != EventKind::EncryptedDirectMessage) // except DMs + .filter(|e| !e.kind.augments_feed_related()) // not augmenting another event .filter(|e| !dismissed.contains(&e.id)) // not dismissed .filter(|e| { if !with_replies { @@ -276,13 +268,8 @@ impl Feed { .events .iter() .filter(|e| e.value().created_at <= now) // no future events - .filter(|e| { - // feed related - e.value().kind == EventKind::TextNote - || e.value().kind == EventKind::EncryptedDirectMessage - || (reposts && (e.value().kind == EventKind::Repost)) - || (long_form && (e.value().kind == EventKind::LongFormContent)) - }) + .filter(|e| kinds.contains(&e.kind)) // feed related + .filter(|e| !e.kind.augments_feed_related()) // not augmenting another event .filter(|e| !dismissed.contains(&e.value().id)) // not dismissed .filter(|e| e.value().pubkey != my_pubkey) // not self-authored .filter(|e| { @@ -335,12 +322,8 @@ impl Feed { let mut events: Vec<(Unixtime, Id)> = GLOBALS .events .iter() - .filter(|e| { - e.value().kind == EventKind::TextNote - || e.value().kind == EventKind::EncryptedDirectMessage - || (reposts && (e.value().kind == EventKind::Repost)) - || (long_form && (e.value().kind == EventKind::LongFormContent)) - }) + .filter(|e| kinds.contains(&e.kind)) // feed related + .filter(|e| !e.kind.augments_feed_related()) // not augmenting another event .filter(|e| e.value().pubkey.as_hex_string() == person_pubkey.as_str()) .filter(|e| !dismissed.contains(&e.value().id)) // not dismissed .map(|e| (e.value().created_at, e.value().id)) diff --git a/src/overlord/minion/handle_websocket.rs b/src/overlord/minion/handle_websocket.rs index 7a8bb81d..9a259c74 100644 --- a/src/overlord/minion/handle_websocket.rs +++ b/src/overlord/minion/handle_websocket.rs @@ -66,21 +66,6 @@ impl Minion { Some(handle), ) .await?; - - /* - if event.kind == EventKind::TextNote { - // Just store text notes in incoming - GLOBALS - .incoming_events - .write() - .await - .push((*event, self.url.clone(), handle)); - } else { - // Process everything else immediately - crate::process::process_new_event(&event, true, Some(self.url.clone())) - .await?; - } - */ } } RelayMessage::Notice(msg) => { diff --git a/src/overlord/minion/mod.rs b/src/overlord/minion/mod.rs index 0244eff4..59254e5b 100644 --- a/src/overlord/minion/mod.rs +++ b/src/overlord/minion/mod.rs @@ -405,9 +405,10 @@ impl Minion { } }; - // Exclude DMs in general feed. - // Allow textnote, deletion, reaction, repost, and long form (if set in settings) - let event_kinds = Self::event_kinds(&[EventKind::EncryptedDirectMessage]); + // Allow all feed related event kinds + let mut event_kinds = GLOBALS.settings.read().feed_related_event_kinds(); + // But exclude DMs in the general feed + event_kinds.retain(|f| *f != EventKind::EncryptedDirectMessage); if let Some(pubkey) = GLOBALS.signer.public_key() { // feed related by me @@ -521,7 +522,8 @@ impl Minion { replies_since.max(one_replieschunk_ago) }; - let event_kinds = Self::event_kinds(&[]); + // Allow all feed related event kinds + let event_kinds = GLOBALS.settings.read().feed_related_event_kinds(); if let Some(pubkey) = GLOBALS.signer.public_key() { // Any mentions of me @@ -581,9 +583,11 @@ impl Minion { async fn subscribe_person_feed(&mut self, pubkey: PublicKeyHex) -> Result<(), Error> { // NOTE we do not unsubscribe to the general feed + // Allow all feed related event kinds + let mut event_kinds = GLOBALS.settings.read().feed_related_event_kinds(); // Exclude DMs and reactions (we wouldn't see the post it reacted to) in person feed - let event_kinds = - Self::event_kinds(&[EventKind::EncryptedDirectMessage, EventKind::Reaction]); + event_kinds + .retain(|f| *f != EventKind::EncryptedDirectMessage && *f != EventKind::Reaction); let filters: Vec = vec![Filter { authors: vec![pubkey.clone().into()], @@ -665,7 +669,11 @@ impl Minion { }); } - let event_kinds = Self::event_kinds(&[EventKind::EncryptedDirectMessage]); + // Allow all feed related event kinds + let mut event_kinds = GLOBALS.settings.read().feed_related_event_kinds(); + // But exclude DMs + event_kinds.retain(|f| *f != EventKind::EncryptedDirectMessage); + filters.push(Filter { e: vec![main], kinds: event_kinds, @@ -942,32 +950,4 @@ impl Minion { Ok(String::from_utf8_unchecked(full.to_vec())) } } - - fn event_kinds(exclude: &[EventKind]) -> Vec { - let (reactions, reposts, show_long_form, direct_messages) = { - let settings = GLOBALS.settings.read(); - ( - settings.reactions, - settings.reposts, - settings.show_long_form, - settings.direct_messages, - ) - }; - - let mut kinds = vec![EventKind::TextNote, EventKind::EventDeletion]; - if reactions && !exclude.contains(&EventKind::Reaction) { - kinds.push(EventKind::Reaction); - } - if reposts && !exclude.contains(&EventKind::Repost) { - kinds.push(EventKind::Repost); - } - if show_long_form && !exclude.contains(&EventKind::LongFormContent) { - kinds.push(EventKind::LongFormContent); - } - if direct_messages && !exclude.contains(&EventKind::EncryptedDirectMessage) { - kinds.push(EventKind::EncryptedDirectMessage); - } - - kinds - } } diff --git a/src/overlord/mod.rs b/src/overlord/mod.rs index 5d7237ad..d8d4a767 100644 --- a/src/overlord/mod.rs +++ b/src/overlord/mod.rs @@ -140,20 +140,19 @@ impl Overlord { let feed_chunk = GLOBALS.settings.read().feed_chunk; let then = now.0 - feed_chunk as i64; - let reactions = if GLOBALS.settings.read().reactions { - " OR kind=7" - } else { - "" - }; - let reposts = if GLOBALS.settings.read().reactions { - " OR kind=6" - } else { - "" - }; + let where_kind = GLOBALS + .settings + .read() + .feed_related_event_kinds() + .iter() + .map(|e| >::into(*e)) + .map(|e| e.to_string()) + .collect::>() + .join(","); let cond = format!( - " (kind=1 OR kind=5{}{}) AND created_at > {} ORDER BY created_at ASC", - reactions, reposts, then + " kind in ({}) AND created_at > {} ORDER BY created_at ASC", + where_kind, then ); let db_events = DbEvent::fetch(Some(&cond)).await?; @@ -1170,15 +1169,6 @@ impl Overlord { // connected to, as well as any relays we discover might know. This is // more than strictly necessary, but not too expensive. - let (enable_reposts, enable_reactions, show_long_form) = { - let settings = GLOBALS.settings.read(); - ( - settings.reposts, - settings.reactions, - settings.show_long_form, - ) - }; - let mut missing_ancestors: Vec = Vec::new(); // Include the relays where the referenced_by event was seen @@ -1241,10 +1231,13 @@ impl Overlord { }) .await?; - let mut kinds = vec![EventKind::EventDeletion]; - if enable_reactions { - kinds.push(EventKind::Reaction); - } + let kinds = GLOBALS + .settings + .read() + .feed_related_event_kinds() + .drain(..) + .filter(|k| k.augments_feed_related()) + .collect(); let e = GLOBALS .events @@ -1259,16 +1252,8 @@ impl Overlord { } } - let mut kinds = vec![EventKind::TextNote, EventKind::EventDeletion]; - if enable_reposts { - kinds.push(EventKind::Repost); - } - if enable_reactions { - kinds.push(EventKind::Reaction); - } - if show_long_form { - kinds.push(EventKind::LongFormContent); - } + let mut kinds = GLOBALS.settings.read().feed_related_event_kinds(); + kinds.retain(|f| *f != EventKind::EncryptedDirectMessage); let e = GLOBALS .events diff --git a/src/process.rs b/src/process.rs index e1fe080b..35896ab3 100644 --- a/src/process.rs +++ b/src/process.rs @@ -19,14 +19,14 @@ pub async fn process_new_event( subscription: Option, ) -> Result<(), Error> { if GLOBALS.events.get(&event.id).is_some() { - tracing::debug!( + tracing::trace!( "{}: Old Event: {} {:?} @{}", seen_on.as_ref().map(|r| r.as_str()).unwrap_or("_"), subscription.unwrap_or("_".to_string()), event.kind, event.created_at ); - // We arleady had this event. + // We already had this event. return Ok(()); } else { tracing::debug!( @@ -52,7 +52,20 @@ pub async fn process_new_event( }; // Save into event table - DbEvent::insert(db_event).await?; + if event.kind.is_replaceable() { + if !DbEvent::replace(db_event).await? { + return Ok(()); // This did not replace anything. + } + } else if event.kind.is_parameterized_replaceable() { + match event.parameter() { + Some(param) => if ! DbEvent::replace_parameterized(db_event, param).await? { + return Ok(()); // This did not replace anything. + }, + None => return Err(Error::General("Parameterized event must have a parameter. This is a code issue, not a data issue".to_owned())), + }; + } else { + DbEvent::insert(db_event).await?; + } } if from_relay { diff --git a/src/settings.rs b/src/settings.rs index 77b5b12f..436a0b00 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,7 +1,7 @@ use crate::error::Error; use crate::globals::GLOBALS; use crate::ui::{Theme, ThemeVariant}; -use nostr_types::PublicKey; +use nostr_types::{EventKind, PublicKey}; use rusqlite::params; use serde::{Deserialize, Serialize}; @@ -279,4 +279,22 @@ impl Settings { Ok(()) } + + pub fn enabled_event_kinds(&self) -> Vec { + EventKind::iter() + .filter(|k| { + ((*k != EventKind::Reaction) || self.reactions) + && ((*k != EventKind::Repost) || self.reposts) + && ((*k != EventKind::LongFormContent) || self.show_long_form) + && ((*k != EventKind::EncryptedDirectMessage) || self.direct_messages) + }) + .collect() + } + + pub fn feed_related_event_kinds(&self) -> Vec { + self.enabled_event_kinds() + .drain(..) + .filter(|k| k.is_feed_related()) + .collect() + } }