notedeck

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

profile.rs (6800B)


      1 use enostr::{FilledKeypair, FullKeypair, ProfileState, Pubkey, RelayPool};
      2 use nostrdb::{Ndb, Note, NoteBuildOptions, NoteBuilder, Transaction};
      3 
      4 use notedeck::{Accounts, ContactState};
      5 use tracing::info;
      6 
      7 use crate::{nav::RouterAction, route::Route};
      8 
      9 pub struct SaveProfileChanges {
     10     pub kp: FullKeypair,
     11     pub state: ProfileState,
     12 }
     13 
     14 impl SaveProfileChanges {
     15     pub fn new(kp: FullKeypair, state: ProfileState) -> Self {
     16         Self { kp, state }
     17     }
     18     pub fn to_note(&self) -> Note {
     19         let sec = &self.kp.secret_key.to_secret_bytes();
     20         add_client_tag(NoteBuilder::new())
     21             .kind(0)
     22             .content(&self.state.to_json())
     23             .options(NoteBuildOptions::default().created_at(true).sign(sec))
     24             .build()
     25             .expect("should build")
     26     }
     27 }
     28 
     29 fn add_client_tag(builder: NoteBuilder<'_>) -> NoteBuilder<'_> {
     30     builder
     31         .start_tag()
     32         .tag_str("client")
     33         .tag_str("Damus Notedeck")
     34 }
     35 
     36 pub enum ProfileAction {
     37     Edit(FullKeypair),
     38     SaveChanges(SaveProfileChanges),
     39     Follow(Pubkey),
     40     Unfollow(Pubkey),
     41 }
     42 
     43 impl ProfileAction {
     44     pub fn process_profile_action(
     45         &self,
     46         ndb: &Ndb,
     47         pool: &mut RelayPool,
     48         accounts: &Accounts,
     49     ) -> Option<RouterAction> {
     50         match self {
     51             ProfileAction::Edit(kp) => Some(RouterAction::route_to(Route::EditProfile(kp.pubkey))),
     52             ProfileAction::SaveChanges(changes) => {
     53                 let note = changes.to_note();
     54                 let Ok(event) = enostr::ClientMessage::event(&note) else {
     55                     tracing::error!("could not serialize profile note?");
     56                     return None;
     57                 };
     58 
     59                 let Ok(json) = event.to_json() else {
     60                     tracing::error!("could not serialize profile note?");
     61                     return None;
     62                 };
     63 
     64                 // TODO(jb55): do this in a more centralized place
     65                 let _ = ndb.process_event_with(&json, nostrdb::IngestMetadata::new().client(true));
     66 
     67                 info!("sending {}", &json);
     68                 pool.send(&event);
     69 
     70                 Some(RouterAction::GoBack)
     71             }
     72             ProfileAction::Follow(target_key) => {
     73                 Self::send_follow_user_event(ndb, pool, accounts, target_key);
     74                 None
     75             }
     76             ProfileAction::Unfollow(target_key) => {
     77                 Self::send_unfollow_user_event(ndb, pool, accounts, target_key);
     78                 None
     79             }
     80         }
     81     }
     82 
     83     fn send_follow_user_event(
     84         ndb: &Ndb,
     85         pool: &mut RelayPool,
     86         accounts: &Accounts,
     87         target_key: &Pubkey,
     88     ) {
     89         send_kind_3_event(ndb, pool, accounts, FollowAction::Follow(target_key));
     90     }
     91 
     92     fn send_unfollow_user_event(
     93         ndb: &Ndb,
     94         pool: &mut RelayPool,
     95         accounts: &Accounts,
     96         target_key: &Pubkey,
     97     ) {
     98         send_kind_3_event(ndb, pool, accounts, FollowAction::Unfollow(target_key));
     99     }
    100 }
    101 
    102 pub fn builder_from_note<F>(note: Note<'_>, skip_tag: Option<F>) -> NoteBuilder<'_>
    103 where
    104     F: Fn(&nostrdb::Tag<'_>) -> bool,
    105 {
    106     let mut builder = NoteBuilder::new();
    107 
    108     builder = builder.content(note.content());
    109     builder = builder.options(NoteBuildOptions::default());
    110     builder = builder.kind(note.kind());
    111     builder = builder.pubkey(note.pubkey());
    112 
    113     for tag in note.tags() {
    114         if let Some(skip) = &skip_tag {
    115             if skip(&tag) {
    116                 continue;
    117             }
    118         }
    119 
    120         builder = builder.start_tag();
    121         for tag_item in tag {
    122             builder = match tag_item.variant() {
    123                 nostrdb::NdbStrVariant::Id(i) => builder.tag_id(i),
    124                 nostrdb::NdbStrVariant::Str(s) => builder.tag_str(s),
    125             };
    126         }
    127     }
    128 
    129     builder
    130 }
    131 
    132 enum FollowAction<'a> {
    133     Follow(&'a Pubkey),
    134     Unfollow(&'a Pubkey),
    135 }
    136 
    137 fn send_kind_3_event(ndb: &Ndb, pool: &mut RelayPool, accounts: &Accounts, action: FollowAction) {
    138     let Some(kp) = accounts.get_selected_account().key.to_full() else {
    139         return;
    140     };
    141 
    142     let txn = Transaction::new(ndb).expect("txn");
    143 
    144     let ContactState::Received {
    145         contacts: _,
    146         note_key,
    147         timestamp: _,
    148     } = accounts.get_selected_account().data.contacts.get_state()
    149     else {
    150         return;
    151     };
    152 
    153     let contact_note = match ndb.get_note_by_key(&txn, *note_key).ok() {
    154         Some(n) => n,
    155         None => {
    156             tracing::error!(
    157                 "Somehow we are in state ContactState::Received but the contact note key doesn't exist"
    158             );
    159             return;
    160         }
    161     };
    162 
    163     if contact_note.kind() != 3 {
    164         tracing::error!(
    165             "Something very wrong just occured. The key for the supposed contact note yielded a note which was not a contact..."
    166         );
    167         return;
    168     }
    169 
    170     let builder = match action {
    171         FollowAction::Follow(pubkey) => {
    172             builder_from_note(contact_note, None::<fn(&nostrdb::Tag<'_>) -> bool>)
    173                 .start_tag()
    174                 .tag_str("p")
    175                 .tag_str(&pubkey.hex())
    176         }
    177         FollowAction::Unfollow(pubkey) => builder_from_note(
    178             contact_note,
    179             Some(|tag: &nostrdb::Tag<'_>| {
    180                 if tag.count() < 2 {
    181                     return false;
    182                 }
    183 
    184                 let Some("p") = tag.get_str(0) else {
    185                     return false;
    186                 };
    187 
    188                 let Some(cur_val) = tag.get_id(1) else {
    189                     return false;
    190                 };
    191 
    192                 cur_val == pubkey.bytes()
    193             }),
    194         ),
    195     };
    196 
    197     send_note_builder(builder, ndb, pool, kp);
    198 }
    199 
    200 fn send_note_builder(builder: NoteBuilder, ndb: &Ndb, pool: &mut RelayPool, kp: FilledKeypair) {
    201     let note = builder
    202         .sign(&kp.secret_key.secret_bytes())
    203         .build()
    204         .expect("build note");
    205 
    206     let Ok(event) = &enostr::ClientMessage::event(&note) else {
    207         tracing::error!("send_note_builder: failed to build json");
    208         return;
    209     };
    210 
    211     let Ok(json) = event.to_json() else {
    212         tracing::error!("send_note_builder: failed to build json");
    213         return;
    214     };
    215 
    216     let _ = ndb.process_event_with(&json, nostrdb::IngestMetadata::new().client(true));
    217     info!("sending {}", &json);
    218     pool.send(event);
    219 }
    220 
    221 pub fn send_new_contact_list(kp: FilledKeypair, ndb: &Ndb, pool: &mut RelayPool) {
    222     let builder = construct_new_contact_list(kp.pubkey);
    223 
    224     send_note_builder(builder, ndb, pool, kp);
    225 }
    226 
    227 fn construct_new_contact_list<'a>(pk: &'a Pubkey) -> NoteBuilder<'a> {
    228     NoteBuilder::new()
    229         .content("")
    230         .kind(3)
    231         .options(NoteBuildOptions::default())
    232         .start_tag()
    233         .tag_str("p")
    234         .tag_str(&pk.hex())
    235 }