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