Files
coop/crates/person/src/person.rs
T
2026-10-12 11:14:03 +00:00

171 lines
4.4 KiB
Rust

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<Announcement>,
/// Messaging relays
messaging_relays: Vec<RelayUrl>,
}
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<Ordering> {
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<H: Hasher>(&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<Tag> {
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<Announcement> {
self.announcement.clone()
}
/// Get profile messaging relays
pub fn messaging_relays(&self) -> &Vec<RelayUrl> {
&self.messaging_relays
}
/// Get relay hint for messaging relay list
pub fn messaging_relay_hint(&self) -> Option<RelayUrl> {
self.messaging_relays.first().cloned()
}
/// Get profile picture, if the profile has one
pub fn avatar(&self) -> Option<SharedString> {
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<I>(&mut self, relays: I)
where
I: IntoIterator<Item = RelayUrl>,
{
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..]
)
}