profile.rs (4323B)
1 use std::collections::HashMap; 2 3 use enostr::{Filter, FullKeypair, Pubkey, PubkeyRef, RelayPool}; 4 use nostrdb::{FilterBuilder, Ndb, Note, NoteBuildOptions, NoteBuilder, ProfileRecord}; 5 6 use notedeck::{filter::default_limit, FilterState}; 7 use tracing::info; 8 9 use crate::{ 10 multi_subscriber::MultiSubscriber, 11 profile_state::ProfileState, 12 route::{Route, Router}, 13 timeline::{PubkeySource, Timeline, TimelineKind, TimelineTab}, 14 }; 15 16 pub struct NostrName<'a> { 17 pub username: Option<&'a str>, 18 pub display_name: Option<&'a str>, 19 pub nip05: Option<&'a str>, 20 } 21 22 impl<'a> NostrName<'a> { 23 pub fn name(&self) -> &'a str { 24 if let Some(name) = self.username { 25 name 26 } else if let Some(name) = self.display_name { 27 name 28 } else { 29 self.nip05.unwrap_or("??") 30 } 31 } 32 33 pub fn unknown() -> Self { 34 Self { 35 username: None, 36 display_name: None, 37 nip05: None, 38 } 39 } 40 } 41 42 fn is_empty(s: &str) -> bool { 43 s.chars().all(|c| c.is_whitespace()) 44 } 45 46 pub fn get_display_name<'a>(record: Option<&ProfileRecord<'a>>) -> NostrName<'a> { 47 if let Some(record) = record { 48 if let Some(profile) = record.record().profile() { 49 let display_name = profile.display_name().filter(|n| !is_empty(n)); 50 let username = profile.name().filter(|n| !is_empty(n)); 51 let nip05 = if let Some(raw_nip05) = profile.nip05() { 52 if let Some(at_pos) = raw_nip05.find('@') { 53 if raw_nip05.starts_with('_') { 54 raw_nip05.get(at_pos + 1..) 55 } else { 56 Some(raw_nip05) 57 } 58 } else { 59 None 60 } 61 } else { 62 None 63 }; 64 65 NostrName { 66 username, 67 display_name, 68 nip05, 69 } 70 } else { 71 NostrName::unknown() 72 } 73 } else { 74 NostrName::unknown() 75 } 76 } 77 78 pub struct Profile { 79 pub timeline: Timeline, 80 pub subscription: Option<MultiSubscriber>, 81 } 82 83 impl Profile { 84 pub fn new(source: PubkeySource, filters: Vec<Filter>) -> Self { 85 let timeline = Timeline::new( 86 TimelineKind::profile(source), 87 FilterState::ready(filters), 88 TimelineTab::full_tabs(), 89 ); 90 91 Profile { 92 timeline, 93 subscription: None, 94 } 95 } 96 97 pub fn filters_raw(pk: PubkeyRef<'_>) -> Vec<FilterBuilder> { 98 vec![Filter::new() 99 .authors([pk.bytes()]) 100 .kinds([1]) 101 .limit(default_limit())] 102 } 103 } 104 105 pub struct SaveProfileChanges { 106 pub kp: FullKeypair, 107 pub state: ProfileState, 108 } 109 110 impl SaveProfileChanges { 111 pub fn new(kp: FullKeypair, state: ProfileState) -> Self { 112 Self { kp, state } 113 } 114 pub fn to_note(&self) -> Note { 115 let sec = &self.kp.secret_key.to_secret_bytes(); 116 add_client_tag(NoteBuilder::new()) 117 .kind(0) 118 .content(&self.state.to_json()) 119 .options(NoteBuildOptions::default().created_at(true).sign(sec)) 120 .build() 121 .expect("should build") 122 } 123 } 124 125 fn add_client_tag(builder: NoteBuilder<'_>) -> NoteBuilder<'_> { 126 builder 127 .start_tag() 128 .tag_str("client") 129 .tag_str("Damus Notedeck") 130 } 131 132 pub enum ProfileAction { 133 Edit(FullKeypair), 134 SaveChanges(SaveProfileChanges), 135 } 136 137 impl ProfileAction { 138 pub fn process( 139 &self, 140 state_map: &mut HashMap<Pubkey, ProfileState>, 141 ndb: &Ndb, 142 pool: &mut RelayPool, 143 router: &mut Router<Route>, 144 ) { 145 match self { 146 ProfileAction::Edit(kp) => { 147 router.route_to(Route::EditProfile(kp.pubkey)); 148 } 149 ProfileAction::SaveChanges(changes) => { 150 let raw_msg = format!("[\"EVENT\",{}]", changes.to_note().json().unwrap()); 151 152 let _ = ndb.process_client_event(raw_msg.as_str()); 153 let _ = state_map.remove_entry(&changes.kp.pubkey); 154 155 info!("sending {}", raw_msg); 156 pool.send(&enostr::ClientMessage::raw(raw_msg)); 157 158 router.go_back(); 159 } 160 } 161 } 162 }