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(¬e) 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(¬e) 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 }