notedeck

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

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 }