notedeck

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

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 }