notedeck

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

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 }