timeline_units.rs (6457B)
1 use std::collections::HashSet; 2 3 use enostr::Pubkey; 4 use nostrdb::{Ndb, Note, NoteKey, Transaction}; 5 use notedeck::NoteRef; 6 use notedeck_ui::note::get_reposted_note; 7 8 use crate::timeline::{ 9 note_units::{InsertManyResponse, NoteUnits}, 10 unit::{ 11 CompositeFragment, NoteUnit, NoteUnitFragment, Reaction, ReactionFragment, RepostFragment, 12 }, 13 }; 14 15 #[derive(Debug, Default)] 16 pub struct TimelineUnits { 17 pub units: NoteUnits, 18 } 19 20 impl TimelineUnits { 21 pub fn with_capacity(cap: usize) -> Self { 22 Self { 23 units: NoteUnits::new_with_cap(cap, false), 24 } 25 } 26 27 pub fn from_refs_single(refs: Vec<NoteRef>) -> Self { 28 let mut entries = TimelineUnits::default(); 29 refs.into_iter().for_each(|r| entries.merge_single_note(r)); 30 entries 31 } 32 33 pub fn len(&self) -> usize { 34 self.units.len() 35 } 36 37 pub fn is_empty(&self) -> bool { 38 self.units.len() == 0 39 } 40 41 /// returns number of new entries merged 42 #[profiling::function] 43 pub fn merge_new_notes<'a>( 44 &mut self, 45 payloads: Vec<&'a NotePayload>, 46 ndb: &Ndb, 47 txn: &Transaction, 48 ) -> MergeResponse<'a> { 49 let mut unknown_pks = HashSet::with_capacity(payloads.len()); 50 let new_fragments = payloads 51 .into_iter() 52 .filter_map(|p| to_fragment(p, ndb, txn)) 53 .map(|f| { 54 if let Some(pk) = f.unknown_pk { 55 unknown_pks.insert(pk); 56 } 57 f.fragment 58 }) 59 .collect(); 60 61 let tl_response = if unknown_pks.is_empty() { 62 None 63 } else { 64 Some(UnknownPks { unknown_pks }) 65 }; 66 67 MergeResponse { 68 insertion_response: self.units.merge_fragments(new_fragments), 69 tl_response, 70 } 71 } 72 73 pub fn latest(&self) -> Option<&NoteRef> { 74 self.units.latest_ref() 75 } 76 77 pub fn merge_single_note(&mut self, note_ref: NoteRef) { 78 self.units.merge_single_unit(note_ref); 79 } 80 81 /// Used in the view 82 pub fn get(&self, index: usize) -> Option<&NoteUnit> { 83 self.units.kth(index) 84 } 85 } 86 87 pub struct MergeResponse<'a> { 88 pub insertion_response: InsertManyResponse, 89 pub tl_response: Option<UnknownPks<'a>>, 90 } 91 92 pub struct UnknownPks<'a> { 93 pub(crate) unknown_pks: HashSet<&'a [u8; 32]>, 94 } 95 96 pub struct NoteUnitFragmentResponse<'a> { 97 pub fragment: NoteUnitFragment, 98 pub unknown_pk: Option<&'a [u8; 32]>, 99 } 100 101 pub struct NotePayload<'a> { 102 pub note: Note<'a>, 103 pub key: NoteKey, 104 } 105 106 impl<'a> NotePayload<'a> { 107 pub fn noteref(&self) -> NoteRef { 108 NoteRef { 109 key: self.key, 110 created_at: self.note.created_at(), 111 } 112 } 113 } 114 115 fn to_fragment<'a>( 116 payload: &'a NotePayload, 117 ndb: &Ndb, 118 txn: &Transaction, 119 ) -> Option<NoteUnitFragmentResponse<'a>> { 120 match payload.note.kind() { 121 1 => Some(NoteUnitFragmentResponse { 122 fragment: NoteUnitFragment::Single(NoteRef { 123 key: payload.key, 124 created_at: payload.note.created_at(), 125 }), 126 unknown_pk: None, 127 }), 128 7 => to_reaction(payload, ndb, txn).map(|r| NoteUnitFragmentResponse { 129 fragment: NoteUnitFragment::Composite(CompositeFragment::Reaction(r.fragment)), 130 unknown_pk: Some(r.pk), 131 }), 132 6 => to_repost(payload, ndb, txn).map(RepostResponse::into), 133 _ => None, 134 } 135 } 136 137 fn to_reaction<'a>( 138 payload: &'a NotePayload, 139 ndb: &Ndb, 140 txn: &Transaction, 141 ) -> Option<ReactionResponse<'a>> { 142 let reaction = payload.note.content(); 143 144 let mut note_reacted_to = None; 145 146 for tag in payload.note.tags() { 147 if tag.count() < 2 { 148 continue; 149 } 150 151 let Some("e") = tag.get_str(0) else { 152 continue; 153 }; 154 155 let Some(react_to_id) = tag.get_id(1) else { 156 continue; 157 }; 158 159 note_reacted_to = Some(react_to_id); 160 } 161 162 let reacted_to_noteid = note_reacted_to?; 163 164 let reaction_note_ref = payload.noteref(); 165 166 let reacted_to_note = ndb.get_note_by_id(txn, reacted_to_noteid).ok()?; 167 168 let noteref_reacted_to = NoteRef { 169 key: reacted_to_note.key()?, 170 created_at: reacted_to_note.created_at(), 171 }; 172 173 let sender_profilekey = ndb 174 .get_profile_by_pubkey(txn, payload.note.pubkey()) 175 .ok() 176 .and_then(|p| p.key()); 177 178 Some(ReactionResponse { 179 fragment: ReactionFragment { 180 noteref_reacted_to, 181 reaction_note_ref, 182 reaction: Reaction { 183 reaction: reaction.to_string(), 184 sender: Pubkey::new(*payload.note.pubkey()), 185 sender_profilekey, 186 }, 187 }, 188 pk: payload.note.pubkey(), 189 }) 190 } 191 192 pub struct ReactionResponse<'a> { 193 fragment: ReactionFragment, 194 pk: &'a [u8; 32], // reaction sender 195 } 196 197 pub struct RepostResponse<'a> { 198 fragment: RepostFragment, 199 reposter_pk: &'a [u8; 32], 200 } 201 202 impl<'a> From<RepostResponse<'a>> for NoteUnitFragmentResponse<'a> { 203 fn from(value: RepostResponse<'a>) -> Self { 204 Self { 205 fragment: NoteUnitFragment::Composite(CompositeFragment::Repost(value.fragment)), 206 unknown_pk: Some(value.reposter_pk), 207 } 208 } 209 } 210 211 fn to_repost<'a>( 212 payload: &'a NotePayload, 213 ndb: &Ndb, 214 txn: &Transaction, 215 ) -> Option<RepostResponse<'a>> { 216 let reposted_note = match get_reposted_note(ndb, txn, &payload.note) { 217 Some(r) => r, 218 None => { 219 tracing::debug!( 220 "Could not get reposted note for note id {}", 221 enostr::NoteId::new(*payload.note.id()).hex() 222 ); 223 return None; 224 } 225 }; 226 227 let reposted_key = match reposted_note.key() { 228 Some(r) => r, 229 None => { 230 tracing::error!( 231 "Could not get key of reposted note {}", 232 enostr::NoteId::new(*reposted_note.id()).hex() 233 ); 234 return None; 235 } 236 }; 237 238 Some(RepostResponse { 239 fragment: RepostFragment { 240 reposted_noteref: NoteRef { 241 key: reposted_key, 242 created_at: reposted_note.created_at(), 243 }, 244 repost_noteref: payload.noteref(), 245 reposter: Pubkey::new(*payload.note.pubkey()), 246 }, 247 reposter_pk: payload.note.pubkey(), 248 }) 249 }