use std::cmp::Ordering; use std::hash::{Hash, Hasher}; use gpui::SharedString; use nostr_sdk::prelude::*; use state::Announcement; /// Person #[derive(Debug, Clone)] pub struct Person { /// Public Key public_key: PublicKey, /// Metadata (profile) metadata: Metadata, /// Custom emoji (NIP-30) from the person's emoji list emojis: Vec<(String, Url)>, /// Dekey (NIP-4e) announcement announcement: Option, /// Messaging relays messaging_relays: Vec, } impl PartialEq for Person { fn eq(&self, other: &Self) -> bool { self.public_key == other.public_key } } impl Eq for Person {} impl PartialOrd for Person { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Person { fn cmp(&self, other: &Self) -> Ordering { self.name().cmp(&other.name()) } } impl Hash for Person { fn hash(&self, state: &mut H) { self.public_key.hash(state) } } impl Person { pub fn new(public_key: PublicKey, metadata: Metadata) -> Self { Self { public_key, metadata, emojis: vec![], announcement: None, messaging_relays: vec![], } } /// Get profile public key pub fn public_key(&self) -> PublicKey { self.public_key } /// Get profile metadata pub fn metadata(&self) -> Metadata { self.metadata.clone() } /// Custom emoji (NIP-30) as (shortcode, image url) pairs pub fn emojis(&self) -> &[(String, Url)] { &self.emojis } /// Set custom emoji from the person's emoji list pub(crate) fn set_emojis(&mut self, emojis: Vec<(String, Url)>) { self.emojis = emojis; } /// An `emoji` tag for every custom emoji referenced as `:shortcode:` in `content` pub fn emoji_tags(&self, content: &str) -> Vec { let mut tags = Vec::new(); for (shortcode, url) in &self.emojis { let pattern = format!(":{shortcode}:"); if content.contains(&pattern) { tags.push(Tag::custom("emoji", [shortcode.clone(), url.to_string()])); } } tags } /// Get profile encryption keys announcement pub fn announcement(&self) -> Option { self.announcement.clone() } /// Get profile messaging relays pub fn messaging_relays(&self) -> &Vec { &self.messaging_relays } /// Get relay hint for messaging relay list pub fn messaging_relay_hint(&self) -> Option { self.messaging_relays.first().cloned() } /// Get profile picture, if the profile has one pub fn avatar(&self) -> Option { self.metadata() .picture .as_ref() .filter(|picture| !picture.is_empty()) .map(SharedString::from) } /// A stable seed for this profile's generated avatar pub fn avatar_seed(&self) -> String { self.public_key().to_hex() } /// Get profile name pub fn name(&self) -> SharedString { if let Some(display_name) = self.metadata().display_name.as_ref() && !display_name.is_empty() { return SharedString::from(display_name.trim()); } if let Some(name) = self.metadata().name.as_ref() && !name.is_empty() { return SharedString::from(name.trim()); } SharedString::from(shorten_pubkey(self.public_key(), 4)) } /// Set profile metadata pub(crate) fn set_metadata(&mut self, metadata: Metadata) { self.metadata = metadata; } /// Set profile encryption keys announcement pub(crate) fn set_announcement(&mut self, announcement: Announcement) { self.announcement = Some(announcement); } /// Set profile messaging relays pub(crate) fn set_messaging_relays(&mut self, relays: I) where I: IntoIterator, { self.messaging_relays = relays.into_iter().collect(); } } /// Shorten a [`PublicKey`] to a string with the first and last `len` characters /// /// Ex. `00000000:00000002` pub fn shorten_pubkey(public_key: PublicKey, len: usize) -> String { let Ok(pubkey) = public_key.to_bech32(); format!( "{}...{}", &pubkey[0..(len + 1)], &pubkey[pubkey.len() - len..] ) }