notedeck

One damus client to rule them all
git clone git://jb55.com/notedeck
Log | Files | Refs | README | LICENSE

commit ba67d65435394a7a12d70fab6ccd0ed9af814cf1
parent eac5d41e3cb1587c65d1fe29cbc23df0ec302ed5
Author: alltheseas <alltheseas@users.noreply.github.com>
Date:   Mon, 10 Nov 2025 20:37:47 -0600

Filter out future-dated notes

Diffstat:
Mcrates/notedeck/src/lib.rs | 5+++--
Mcrates/notedeck/src/time.rs | 32++++++++++++++++++++++++++++----
Mcrates/notedeck_columns/src/actionbar.rs | 7+++++++
Mcrates/notedeck_columns/src/timeline/mod.rs | 14+++++++++++++-
4 files changed, 51 insertions(+), 7 deletions(-)

diff --git a/crates/notedeck/src/lib.rs b/crates/notedeck/src/lib.rs @@ -85,8 +85,9 @@ pub use route::DrawerRouter; pub use storage::{AccountStorage, DataPath, DataPathType, Directory}; pub use style::NotedeckTextStyle; pub use theme::ColorTheme; -pub use time::time_ago_since; -pub use time::time_format; +pub use time::{ + is_future_timestamp, time_ago_since, time_format, unix_time_secs, MAX_FUTURE_NOTE_SKEW_SECS, +}; pub use timecache::TimeCached; pub use unknowns::{get_unknown_note_ids, NoteRefsUnkIdAction, SingleUnkIdAction, UnknownIds}; pub use urls::{supported_mime_hosted_at_url, SupportedMimeType, UrlMimes}; diff --git a/crates/notedeck/src/time.rs b/crates/notedeck/src/time.rs @@ -10,6 +10,22 @@ const ONE_WEEK_IN_SECONDS: u64 = 604_800; const ONE_MONTH_IN_SECONDS: u64 = 2_592_000; // 30 days const ONE_YEAR_IN_SECONDS: u64 = 31_536_000; // 365 days +/// Maximum tolerated skew for note timestamps in the future (24h in seconds). +pub const MAX_FUTURE_NOTE_SKEW_SECS: u64 = ONE_DAY_IN_SECONDS; + +/// Returns the current UNIX timestamp in seconds. +pub fn unix_time_secs() -> u64 { + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() +} + +/// Whether `timestamp` is further in the future than the allowed skew. +pub fn is_future_timestamp(timestamp: u64, now: u64) -> bool { + timestamp > now + MAX_FUTURE_NOTE_SKEW_SECS +} + /// Calculate relative time between two timestamps, with two units only /// when the scale is large enough (e.g., "1y 6m", "5d 4h"), /// but not for hours/minutes/seconds. @@ -95,10 +111,7 @@ pub fn time_format(_i18n: &mut Localization, timestamp: u64) -> String { } pub fn time_ago_since(i18n: &mut Localization, timestamp: u64) -> String { - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("Time went backwards") - .as_secs(); + let now = unix_time_secs(); time_ago_between(i18n, timestamp, now) } @@ -354,4 +367,15 @@ mod tests { result ); } + + #[test] + fn test_future_skew_helper() { + let now = 1_000_000u64; + assert!(!is_future_timestamp(now, now)); + assert!(!is_future_timestamp(now + MAX_FUTURE_NOTE_SKEW_SECS - 1, now)); + assert!(is_future_timestamp( + now + MAX_FUTURE_NOTE_SKEW_SECS + 1, + now + )); + } } diff --git a/crates/notedeck_columns/src/actionbar.rs b/crates/notedeck_columns/src/actionbar.rs @@ -16,7 +16,9 @@ use enostr::{FilledKeypair, NoteId, Pubkey, RelayPool}; use nostrdb::{IngestMetadata, Ndb, NoteBuilder, NoteKey, Transaction}; use notedeck::{ get_wallet_for, + is_future_timestamp, note::{reaction_sent_id, ReactAction, ZapTargetAmount}, + unix_time_secs, Accounts, GlobalWallet, Images, NoteAction, NoteCache, NoteZapTargetOwned, UnknownIds, ZapAction, ZapTarget, ZappingError, Zaps, }; @@ -506,6 +508,7 @@ pub fn process_thread_notes( return; } + let now = unix_time_secs(); let mut has_spliced_resp = false; let mut num_new_notes = 0; for key in notes { @@ -519,6 +522,10 @@ pub fn process_thread_notes( continue; }; + if is_future_timestamp(note.created_at(), now) { + continue; + } + // Ensure that unknown ids are captured when inserting notes UnknownIds::update_from_note(txn, ndb, unknown_ids, note_cache, &note); diff --git a/crates/notedeck_columns/src/timeline/mod.rs b/crates/notedeck_columns/src/timeline/mod.rs @@ -9,7 +9,9 @@ use crate::{ use notedeck::{ contacts::hybrid_contacts_filter, filter::{self, HybridFilter}, - tr, Accounts, CachedNote, ContactState, FilterError, FilterState, FilterStates, Localization, + is_future_timestamp, tr, + unix_time_secs, + Accounts, CachedNote, ContactState, FilterError, FilterState, FilterStates, Localization, NoteCache, NoteRef, UnknownIds, }; @@ -368,8 +370,13 @@ impl Timeline { filters }; + let now = unix_time_secs(); let mut unknown_pks = HashSet::new(); for note_ref in notes { + if is_future_timestamp(note_ref.created_at, now) { + continue; + } + for (view, filter) in filters.iter().enumerate() { if let Ok(note) = ndb.get_note_by_key(txn, note_ref.key) { if filter( @@ -417,6 +424,7 @@ impl Timeline { reversed: bool, ) -> Result<()> { let mut payloads: Vec<NotePayload> = Vec::with_capacity(new_note_ids.len()); + let now = unix_time_secs(); for key in new_note_ids { let note = if let Ok(note) = ndb.get_note_by_key(txn, *key) { @@ -429,6 +437,10 @@ impl Timeline { continue; }; + if is_future_timestamp(note.created_at(), now) { + continue; + } + // Ensure that unknown ids are captured when inserting notes // into the timeline UnknownIds::update_from_note(txn, ndb, unknown_ids, note_cache, &note);