diff --git a/crates/chat/src/room.rs b/crates/chat/src/room.rs index d9ebced..192c0d9 100644 --- a/crates/chat/src/room.rs +++ b/crates/chat/src/room.rs @@ -4,11 +4,11 @@ use std::hash::{Hash, Hasher}; use std::time::Duration; use anyhow::Error; -use common::{EventUtils, RenderedProfile}; +use common::EventUtils; use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task}; use itertools::Itertools; use nostr_sdk::prelude::*; -use person::PersonRegistry; +use person::{Person, PersonRegistry}; use state::{tracker, NostrRegistry}; use crate::NewMessage; @@ -264,9 +264,9 @@ impl Room { } /// Gets the display image for the room - pub fn display_image(&self, proxy: bool, cx: &App) -> SharedString { + pub fn display_image(&self, cx: &App) -> SharedString { if !self.is_group() { - self.display_member(cx).avatar(proxy) + self.display_member(cx).avatar() } else { SharedString::from("brand/group.png") } @@ -275,7 +275,7 @@ impl Room { /// Get a member to represent the room /// /// Display member is always different from the current user. - pub fn display_member(&self, cx: &App) -> Profile { + pub fn display_member(&self, cx: &App) -> Person { let persons = PersonRegistry::global(cx); let nostr = NostrRegistry::global(cx); let public_key = nostr.read(cx).identity().read(cx).public_key(); @@ -295,7 +295,7 @@ impl Room { let persons = PersonRegistry::global(cx); if self.is_group() { - let profiles: Vec = self + let profiles: Vec = self .members .iter() .map(|public_key| persons.read(cx).get(public_key, cx)) @@ -314,7 +314,7 @@ impl Room { SharedString::from(name) } else { - self.display_member(cx).display_name() + self.display_member(cx).name() } } diff --git a/crates/chat_ui/src/lib.rs b/crates/chat_ui/src/lib.rs index 1d9753c..5a001da 100644 --- a/crates/chat_ui/src/lib.rs +++ b/crates/chat_ui/src/lib.rs @@ -3,7 +3,7 @@ use std::time::Duration; pub use actions::*; use chat::{Message, RenderedMessage, Room, RoomEvent, RoomKind, SendReport}; -use common::{nip96_upload, RenderedProfile, RenderedTimestamp}; +use common::{nip96_upload, RenderedTimestamp}; use gpui::prelude::FluentBuilder; use gpui::{ div, img, list, px, red, relative, rems, svg, white, AnyElement, App, AppContext, @@ -16,7 +16,7 @@ use gpui_tokio::Tokio; use indexset::{BTreeMap, BTreeSet}; use itertools::Itertools; use nostr_sdk::prelude::*; -use person::PersonRegistry; +use person::{Person, PersonRegistry}; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; use smol::fs; @@ -506,7 +506,7 @@ impl ChatPanel { }); } - fn profile(&self, public_key: &PublicKey, cx: &Context) -> Profile { + fn profile(&self, public_key: &PublicKey, cx: &Context) -> Person { let persons = PersonRegistry::global(cx); persons.read(cx).get(public_key, cx) } @@ -632,7 +632,7 @@ impl ChatPanel { this.child( div() .id(SharedString::from(format!("{ix}-avatar"))) - .child(Avatar::new(author.avatar(proxy)).size(rems(2.))) + .child(Avatar::new(author.avatar()).size(rems(2.))) .context_menu(move |this, _window, _cx| { let view = Box::new(OpenPublicKey(public_key)); let copy = Box::new(CopyPublicKey(public_key)); @@ -657,7 +657,7 @@ impl ChatPanel { div() .font_semibold() .text_color(cx.theme().text) - .child(author.display_name()), + .child(author.name()), ) .child(message.created_at.to_human_time()) .when_some(is_sent_success, |this, status| { @@ -714,7 +714,7 @@ impl ChatPanel { .child( div() .text_color(cx.theme().text_accent) - .child(author.display_name()), + .child(author.name()), ) .child( div() @@ -796,8 +796,8 @@ impl ChatPanel { fn render_report(report: &SendReport, cx: &App) -> impl IntoElement { let persons = PersonRegistry::global(cx); let profile = persons.read(cx).get(&report.receiver, cx); - let name = profile.display_name(); - let avatar = profile.avatar(true); + let name = profile.name(); + let avatar = profile.avatar(); v_flex() .gap_2() @@ -1080,7 +1080,7 @@ impl ChatPanel { .child( div() .text_color(cx.theme().text_accent) - .child(profile.display_name()), + .child(profile.name()), ), ) .child( @@ -1134,7 +1134,7 @@ impl Panel for ChatPanel { .read_with(cx, |this, cx| { let proxy = AppSettings::get_proxy_user_avatars(cx); let label = this.display_name(cx); - let url = this.display_image(proxy, cx); + let url = this.display_image(cx); h_flex() .gap_1p5() diff --git a/crates/chat_ui/src/text.rs b/crates/chat_ui/src/text.rs index 77e0dbb..9632dfb 100644 --- a/crates/chat_ui/src/text.rs +++ b/crates/chat_ui/src/text.rs @@ -1,7 +1,6 @@ use std::ops::Range; use std::sync::Arc; -use common::RenderedProfile; use gpui::{ AnyElement, App, ElementId, HighlightStyle, InteractiveText, IntoElement, SharedString, StyledText, UnderlineStyle, Window, @@ -255,7 +254,7 @@ fn render_pubkey( ) { let persons = PersonRegistry::global(cx); let profile = persons.read(cx).get(&public_key, cx); - let display_name = format!("@{}", profile.display_name()); + let display_name = format!("@{}", profile.name()); text.replace_range(range.clone(), &display_name); diff --git a/crates/coop/src/chatspace.rs b/crates/coop/src/chatspace.rs index 168806d..dddb4a2 100644 --- a/crates/coop/src/chatspace.rs +++ b/crates/coop/src/chatspace.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use auto_update::{AutoUpdateStatus, AutoUpdater}; use chat::{ChatEvent, ChatRegistry}; use chat_ui::{CopyPublicKey, OpenPublicKey}; -use common::{RenderedProfile, DEFAULT_SIDEBAR_WIDTH}; +use common::DEFAULT_SIDEBAR_WIDTH; use gpui::prelude::FluentBuilder; use gpui::{ deferred, div, px, relative, rems, App, AppContext, Axis, ClipboardItem, Context, Entity, @@ -14,7 +14,6 @@ use key_store::{Credential, KeyItem, KeyStore}; use nostr_connect::prelude::*; use person::PersonRegistry; use relay_auth::RelayAuth; -use settings::AppSettings; use smallvec::{smallvec, SmallVec}; use state::NostrRegistry; use theme::{ActiveTheme, Theme, ThemeMode, ThemeRegistry}; @@ -257,9 +256,9 @@ impl ChatSpace { this.update_in(cx, |_, window, cx| { match result { - Ok(profile) => { + Ok(person) => { persons.update(cx, |this, cx| { - this.insert(profile, cx); + this.insert(person, cx); // Close the edit profile modal window.close_all_modals(cx); }); @@ -476,7 +475,6 @@ impl ChatSpace { } fn titlebar_right(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let proxy = AppSettings::get_proxy_user_avatars(cx); let auto_update = AutoUpdater::global(cx); let relay_auth = RelayAuth::global(cx); @@ -562,9 +560,9 @@ impl ChatSpace { .reverse() .transparent() .icon(IconName::CaretDown) - .child(Avatar::new(profile.avatar(proxy)).size(rems(1.45))) + .child(Avatar::new(profile.avatar()).size(rems(1.45))) .popup_menu(move |this, _window, _cx| { - this.label(profile.display_name()) + this.label(profile.name()) .menu_with_icon( "Profile", IconName::EmojiFill, diff --git a/crates/coop/src/sidebar/mod.rs b/crates/coop/src/sidebar/mod.rs index 84058f2..bfb823b 100644 --- a/crates/coop/src/sidebar/mod.rs +++ b/crates/coop/src/sidebar/mod.rs @@ -13,7 +13,6 @@ use gpui::{ use gpui_tokio::Tokio; use list_item::RoomListItem; use nostr_sdk::prelude::*; -use settings::AppSettings; use smallvec::{smallvec, SmallVec}; use state::{NostrRegistry, GIFTWRAP_SUBSCRIPTION}; use theme::ActiveTheme; @@ -540,7 +539,6 @@ impl Sidebar { range: Range, cx: &Context, ) -> Vec { - let proxy = AppSettings::get_proxy_user_avatars(cx); let mut items = Vec::with_capacity(range.end - range.start); for ix in range { @@ -563,7 +561,7 @@ impl Sidebar { RoomListItem::new(ix) .room_id(room_id) .name(this.display_name(cx)) - .avatar(this.display_image(proxy, cx)) + .avatar(this.display_image(cx)) .public_key(member.public_key()) .kind(this.kind) .created_at(this.created_at.to_ago()) diff --git a/crates/coop/src/user/mod.rs b/crates/coop/src/user/mod.rs index 34207bb..7d606c4 100644 --- a/crates/coop/src/user/mod.rs +++ b/crates/coop/src/user/mod.rs @@ -10,6 +10,7 @@ use gpui::{ }; use gpui_tokio::Tokio; use nostr_sdk::prelude::*; +use person::Person; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; use smol::fs; @@ -233,7 +234,7 @@ impl UserProfile { .detach(); } - pub fn set_metadata(&mut self, cx: &mut Context) -> Task> { + pub fn set_metadata(&mut self, cx: &mut Context) -> Task> { let avatar = self.avatar_input.read(cx).value().to_string(); let name = self.name_input.read(cx).value().to_string(); let bio = self.bio_input.read(cx).value().to_string(); @@ -274,7 +275,7 @@ impl UserProfile { // Return the updated profile let metadata = Metadata::from_json(&event.content).unwrap_or_default(); - let profile = Profile::new(event.pubkey, metadata); + let profile = Person::new(event.pubkey, metadata); Ok(profile) }) diff --git a/crates/coop/src/user/viewer.rs b/crates/coop/src/user/viewer.rs index 1abfc17..e710dba 100644 --- a/crates/coop/src/user/viewer.rs +++ b/crates/coop/src/user/viewer.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use common::{nip05_verify, shorten_pubkey, RenderedProfile}; +use common::{nip05_verify, shorten_pubkey}; use gpui::prelude::FluentBuilder; use gpui::{ div, relative, rems, App, AppContext, ClipboardItem, Context, Entity, IntoElement, @@ -8,8 +8,7 @@ use gpui::{ }; use gpui_tokio::Tokio; use nostr_sdk::prelude::*; -use person::PersonRegistry; -use settings::AppSettings; +use person::{Person, PersonRegistry}; use smallvec::{smallvec, SmallVec}; use state::NostrRegistry; use theme::ActiveTheme; @@ -23,7 +22,7 @@ pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity< #[derive(Debug)] pub struct ProfileViewer { - profile: Profile, + profile: Person, /// Follow status followed: bool, @@ -134,7 +133,6 @@ impl ProfileViewer { impl Render for ProfileViewer { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let proxy = AppSettings::get_proxy_user_avatars(cx); let bech32 = shorten_pubkey(self.profile.public_key(), 16); let shared_bech32 = SharedString::from(bech32); @@ -147,14 +145,14 @@ impl Render for ProfileViewer { .items_center() .justify_center() .text_center() - .child(Avatar::new(self.profile.avatar(proxy)).size(rems(4.))) + .child(Avatar::new(self.profile.avatar()).size(rems(4.))) .child( v_flex() .child( div() .font_semibold() .line_height(relative(1.25)) - .child(self.profile.display_name()), + .child(self.profile.name()), ) .when_some(self.address(cx), |this, address| { this.child( diff --git a/crates/coop/src/views/compose.rs b/crates/coop/src/views/compose.rs index 9742bd7..cca222c 100644 --- a/crates/coop/src/views/compose.rs +++ b/crates/coop/src/views/compose.rs @@ -3,7 +3,7 @@ use std::time::Duration; use anyhow::{anyhow, Error}; use chat::{ChatRegistry, Room}; -use common::{nip05_profile, RenderedProfile, TextUtils, BOOTSTRAP_RELAYS}; +use common::{nip05_profile, TextUtils, BOOTSTRAP_RELAYS}; use gpui::prelude::FluentBuilder; use gpui::{ div, px, relative, rems, uniform_list, App, AppContext, Context, Entity, InteractiveElement, @@ -13,7 +13,6 @@ use gpui::{ use gpui_tokio::Tokio; use nostr_sdk::prelude::*; use person::PersonRegistry; -use settings::AppSettings; use smallvec::{smallvec, SmallVec}; use state::NostrRegistry; use theme::ActiveTheme; @@ -359,7 +358,6 @@ impl Compose { } fn list_items(&self, range: Range, cx: &Context) -> Vec { - let proxy = AppSettings::get_proxy_user_avatars(cx); let persons = PersonRegistry::global(cx); let mut items = Vec::with_capacity(self.contacts.read(cx).len()); @@ -383,8 +381,8 @@ impl Compose { h_flex() .gap_1p5() .text_sm() - .child(Avatar::new(profile.avatar(proxy)).size(rems(1.75))) - .child(profile.display_name()), + .child(Avatar::new(profile.avatar()).size(rems(1.75))) + .child(profile.name()), ) .when(contact.selected, |this| { this.child( diff --git a/crates/coop/src/views/screening.rs b/crates/coop/src/views/screening.rs index bc9a8fd..0de27eb 100644 --- a/crates/coop/src/views/screening.rs +++ b/crates/coop/src/views/screening.rs @@ -8,8 +8,7 @@ use gpui::{ }; use gpui_tokio::Tokio; use nostr_sdk::prelude::*; -use person::PersonRegistry; -use settings::AppSettings; +use person::{Person, PersonRegistry}; use smallvec::{smallvec, SmallVec}; use state::NostrRegistry; use theme::ActiveTheme; @@ -23,7 +22,7 @@ pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity< } pub struct Screening { - profile: Profile, + profile: Person, verified: bool, followed: bool, last_active: Option, @@ -225,7 +224,6 @@ impl Screening { impl Render for Screening { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let proxy = AppSettings::get_proxy_user_avatars(cx); let shorten_pubkey = shorten_pubkey(self.profile.public_key(), 8); let total_mutuals = self.mutual_contacts.len(); let last_active = self.last_active.map(|_| true); @@ -238,12 +236,12 @@ impl Render for Screening { .items_center() .justify_center() .text_center() - .child(Avatar::new(self.profile.avatar(proxy)).size(rems(4.))) + .child(Avatar::new(self.profile.avatar()).size(rems(4.))) .child( div() .font_semibold() .line_height(relative(1.25)) - .child(self.profile.display_name()), + .child(self.profile.name()), ), ) .child( diff --git a/crates/coop/src/views/startup.rs b/crates/coop/src/views/startup.rs index b83fd48..a79aa1c 100644 --- a/crates/coop/src/views/startup.rs +++ b/crates/coop/src/views/startup.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use common::{RenderedProfile, BUNKER_TIMEOUT}; +use common::BUNKER_TIMEOUT; use gpui::prelude::FluentBuilder; use gpui::{ div, relative, rems, svg, AnyElement, App, AppContext, Context, Entity, EventEmitter, @@ -266,8 +266,8 @@ impl Render for Startup { ) }) .when(!self.loading, |this| { - let avatar = profile.avatar(true); - let name = profile.display_name(); + let avatar = profile.avatar(); + let name = profile.name(); this.child( h_flex() diff --git a/crates/person/src/lib.rs b/crates/person/src/lib.rs index f04b61b..e214cd3 100644 --- a/crates/person/src/lib.rs +++ b/crates/person/src/lib.rs @@ -7,9 +7,12 @@ use anyhow::{anyhow, Error}; use common::{EventUtils, BOOTSTRAP_RELAYS}; use gpui::{App, AppContext, Context, Entity, Global, Task}; use nostr_sdk::prelude::*; +pub use person::*; use smallvec::{smallvec, SmallVec}; use state::{NostrRegistry, TIMEOUT}; +mod person; + pub fn init(cx: &mut App) { PersonRegistry::set_global(cx.new(PersonRegistry::new), cx); } @@ -22,7 +25,7 @@ impl Global for GlobalPersonRegistry {} #[derive(Debug)] pub struct PersonRegistry { /// Collection of all persons (user profiles) - persons: HashMap>, + persons: HashMap>, /// Set of public keys that have been seen seen: Rc>>, @@ -51,7 +54,7 @@ impl PersonRegistry { let client = nostr.read(cx).client(); // Channel for communication between nostr and gpui - let (tx, rx) = flume::bounded::(100); + let (tx, rx) = flume::bounded::(100); let (mta_tx, mta_rx) = flume::bounded::(100); let mut tasks = smallvec![]; @@ -81,9 +84,9 @@ impl PersonRegistry { tasks.push( // Update GPUI state cx.spawn(async move |this, cx| { - while let Ok(profile) = rx.recv_async().await { + while let Ok(person) = rx.recv_async().await { this.update(cx, |this, cx| { - this.insert(profile, cx); + this.insert(person, cx); }) .ok(); } @@ -99,9 +102,9 @@ impl PersonRegistry { .await; match result { - Ok(profiles) => { + Ok(persons) => { this.update(cx, |this, cx| { - this.bulk_inserts(profiles, cx); + this.bulk_inserts(persons, cx); }) .ok(); } @@ -121,7 +124,7 @@ impl PersonRegistry { } /// Handle nostr notifications - async fn handle_notifications(client: &Client, tx: &flume::Sender) { + async fn handle_notifications(client: &Client, tx: &flume::Sender) { let mut notifications = client.notifications(); let mut processed_events = HashSet::new(); @@ -140,9 +143,9 @@ impl PersonRegistry { match event.kind { Kind::Metadata => { let metadata = Metadata::from_json(&event.content).unwrap_or_default(); - let profile = Profile::new(event.pubkey, metadata); + let person = Person::new(event.pubkey, metadata); - tx.send_async(profile).await.ok(); + tx.send_async(person).await.ok(); } Kind::ContactList => { let public_keys = event.extract_public_keys(); @@ -214,51 +217,50 @@ impl PersonRegistry { } /// Load all user profiles from the database - async fn load_persons(client: &Client) -> Result, Error> { + async fn load_persons(client: &Client) -> Result, Error> { let filter = Filter::new().kind(Kind::Metadata).limit(200); let events = client.database().query(filter).await?; - let mut profiles = vec![]; + let mut persons = vec![]; for event in events.into_iter() { let metadata = Metadata::from_json(event.content).unwrap_or_default(); - let profile = Profile::new(event.pubkey, metadata); - profiles.push(profile); + let person = Person::new(event.pubkey, metadata); + persons.push(person); } - Ok(profiles) + Ok(persons) } /// Insert batch of persons - fn bulk_inserts(&mut self, profiles: Vec, cx: &mut Context) { - for profile in profiles.into_iter() { - self.persons - .insert(profile.public_key(), cx.new(|_| profile)); + fn bulk_inserts(&mut self, persons: Vec, cx: &mut Context) { + for person in persons.into_iter() { + self.persons.insert(person.public_key(), cx.new(|_| person)); } cx.notify(); } /// Insert or update a person - pub fn insert(&mut self, profile: Profile, cx: &mut App) { - let public_key = profile.public_key(); + pub fn insert(&mut self, person: Person, cx: &mut App) { + let public_key = person.public_key(); match self.persons.get(&public_key) { - Some(person) => { - person.update(cx, |this, cx| { - *this = profile; + Some(this) => { + this.update(cx, |this, cx| { + *this = person; cx.notify(); }); } None => { - self.persons.insert(public_key, cx.new(|_| profile)); + self.persons.insert(public_key, cx.new(|_| person)); } } } /// Get single person by public key - pub fn get(&self, public_key: &PublicKey, cx: &App) -> Profile { - if let Some(profile) = self.persons.get(public_key) { - return profile.read(cx).clone(); + pub fn get(&self, public_key: &PublicKey, cx: &App) -> Person { + if let Some(person) = self.persons.get(public_key) { + return person.read(cx).clone(); } let public_key = *public_key; @@ -277,6 +279,6 @@ impl PersonRegistry { } // Return a temporary profile with default metadata - Profile::new(public_key, Metadata::default()) + Person::new(public_key, Metadata::default()) } } diff --git a/crates/person/src/person.rs b/crates/person/src/person.rs new file mode 100644 index 0000000..0362825 --- /dev/null +++ b/crates/person/src/person.rs @@ -0,0 +1,111 @@ +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: PublicKey, + metadata: Metadata, + announcement: Option, +} + +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 From for Person { + fn from(public_key: PublicKey) -> Self { + Self::new(public_key, Metadata::default()) + } +} + +impl Person { + pub fn new(public_key: PublicKey, metadata: Metadata) -> Self { + Self { + public_key, + metadata, + announcement: None, + } + } + + /// Get profile public key + pub fn public_key(&self) -> PublicKey { + self.public_key + } + + /// Get profile metadata + pub fn metadata(&self) -> Metadata { + self.metadata.clone() + } + + /// Get profile encryption keys announcement + pub fn announcement(&self) -> Option { + self.announcement.clone() + } + + /// Get profile avatar + pub fn avatar(&self) -> SharedString { + self.metadata() + .picture + .as_ref() + .filter(|picture| !picture.is_empty()) + .map(|picture| picture.into()) + .unwrap_or_else(|| "brand/avatar.png".into()) + } + + /// Get profile name + pub fn name(&self) -> SharedString { + if let Some(display_name) = self.metadata().display_name.as_ref() { + if !display_name.is_empty() { + return SharedString::from(display_name); + } + } + + if let Some(name) = self.metadata().name.as_ref() { + if !name.is_empty() { + return SharedString::from(name); + } + } + + SharedString::from(shorten_pubkey(self.public_key(), 4)) + } +} + +/// 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..] + ) +} diff --git a/crates/state/src/device.rs b/crates/state/src/device.rs index d0a1ab7..798cb5f 100644 --- a/crates/state/src/device.rs +++ b/crates/state/src/device.rs @@ -8,6 +8,26 @@ pub struct Announcement { client_name: Option, } +impl From<&Event> for Announcement { + fn from(val: &Event) -> Self { + let public_key = val + .tags + .iter() + .find(|tag| tag.kind().as_str() == "n" || tag.kind().as_str() == "P") + .and_then(|tag| tag.content()) + .and_then(|c| PublicKey::parse(c).ok()) + .unwrap_or(val.pubkey); + + let client_name = val + .tags + .find(TagKind::Client) + .and_then(|tag| tag.content()) + .map(|c| c.to_string()); + + Self::new(val.id, client_name, public_key) + } +} + impl Announcement { pub fn new(id: EventId, client_name: Option, public_key: PublicKey) -> Self { Self { diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index fdebe53..de90e9e 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -528,8 +528,18 @@ impl NostrRegistry { .limit(1) .author(public_key); + // Filter for encryption keys announcement + let encryption_keys = Filter::new() + .kind(Kind::Custom(10044)) + .limit(1) + .author(public_key); + client - .subscribe_to(urls, vec![metadata, contact_list], Some(opts)) + .subscribe_to( + urls, + vec![metadata, contact_list, encryption_keys], + Some(opts), + ) .await?; Ok(())