lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit 4968ac9ef76b64ec646e3ff968efdd3d1d8f378f
parent 1fc64c9ff65b3b92b3452f3c85993b7aea81eac0
Author: triesap <tyson@radroots.org>
Date:   Sun, 15 Feb 2026 18:11:43 +0000

nostr: add shared presence bootstrap and metadata helper exports


- export metadata field detection helper for shared service startup checks
- add shared presence bootstrap flow for connect profile metadata and handler publishing
- include profile-type tags when publishing fallback metadata events
- export new presence helper from prelude behind client codec and events features

Diffstat:
Mnostr/src/events/application_handler.rs | 24++++++++++++++++++++++--
Mnostr/src/identity_profile.rs | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mnostr/src/lib.rs | 4++++
3 files changed, 80 insertions(+), 2 deletions(-)

diff --git a/nostr/src/events/application_handler.rs b/nostr/src/events/application_handler.rs @@ -56,7 +56,7 @@ pub fn radroots_nostr_build_application_handler_event( let mut content = String::new(); if let Some(md) = spec.metadata.as_ref() { - if metadata_has_fields(md) { + if radroots_nostr_metadata_has_fields(md) { content = serde_json::to_string(md).unwrap_or_default(); } } @@ -89,7 +89,7 @@ pub fn radroots_nostr_build_application_handler_event( radroots_nostr_build_event(KIND_APPLICATION_HANDLER, content, tags) } -fn metadata_has_fields(md: &RadrootsNostrMetadata) -> bool { +pub fn radroots_nostr_metadata_has_fields(md: &RadrootsNostrMetadata) -> bool { md.name.is_some() || md.display_name.is_some() || md.about.is_some() @@ -145,3 +145,23 @@ fn tag_value(event: &RadrootsNostrEvent, key: &str) -> Option<String> { .iter() .find_map(|tag| radroots_nostr_tag_first_value(tag, key)) } + +#[cfg(test)] +mod tests { + use super::radroots_nostr_metadata_has_fields; + use crate::types::RadrootsNostrMetadata; + + #[test] + fn metadata_has_fields_false_when_empty() { + assert!(!radroots_nostr_metadata_has_fields( + &RadrootsNostrMetadata::default() + )); + } + + #[test] + fn metadata_has_fields_true_when_about_is_set() { + let mut metadata = RadrootsNostrMetadata::default(); + metadata.about = Some("ready".to_string()); + assert!(radroots_nostr_metadata_has_fields(&metadata)); + } +} diff --git a/nostr/src/identity_profile.rs b/nostr/src/identity_profile.rs @@ -1,9 +1,18 @@ use crate::client::RadrootsNostrClient; use crate::error::RadrootsNostrError; +#[cfg(feature = "events")] +use crate::events::application_handler::{ + RadrootsNostrApplicationHandlerSpec, radroots_nostr_metadata_has_fields, + radroots_nostr_publish_application_handler, +}; use crate::events::metadata::radroots_nostr_build_metadata_event; +#[cfg(feature = "events")] +use crate::types::RadrootsNostrMetadata; use crate::types::{ RadrootsNostrEventId, RadrootsNostrOutput, RadrootsNostrTag, RadrootsNostrTagKind, }; +#[cfg(feature = "events")] +use core::time::Duration; use radroots_events::profile::RadrootsProfileType; use radroots_events_codec::profile::encode::profile_build_tags; use radroots_identity::RadrootsIdentity; @@ -40,3 +49,48 @@ pub async fn radroots_nostr_publish_identity_profile_with_type( let out = client.send_event_builder(builder).await?; Ok(Some(out)) } + +#[cfg(feature = "events")] +pub async fn radroots_nostr_bootstrap_service_presence( + client: &RadrootsNostrClient, + identity: &RadrootsIdentity, + profile_type: Option<RadrootsProfileType>, + metadata: &RadrootsNostrMetadata, + handler_spec: &RadrootsNostrApplicationHandlerSpec, + connect_timeout: Duration, +) -> Result<(), RadrootsNostrError> { + client.connect().await; + client.wait_for_connection(connect_timeout).await; + + let profile_published = + match radroots_nostr_publish_identity_profile_with_type(client, identity, profile_type) + .await? + { + Some(_) => true, + None => false, + }; + + if radroots_nostr_metadata_has_fields(metadata) && !profile_published { + let builder = + radroots_nostr_build_metadata_event(metadata).tags(profile_type_tags(profile_type)); + client.send_event_builder(builder).await?; + } + + radroots_nostr_publish_application_handler(client, handler_spec).await?; + Ok(()) +} + +fn profile_type_tags(profile_type: Option<RadrootsProfileType>) -> Vec<RadrootsNostrTag> { + let mut tag_list: Vec<RadrootsNostrTag> = Vec::new(); + for mut tag in profile_build_tags(profile_type) { + if tag.is_empty() { + continue; + } + let key = tag.remove(0); + tag_list.push(RadrootsNostrTag::custom( + RadrootsNostrTagKind::Custom(key.into()), + tag, + )); + } + tag_list +} diff --git a/nostr/src/lib.rs b/nostr/src/lib.rs @@ -63,6 +63,7 @@ pub mod prelude { #[cfg(feature = "events")] pub use crate::events::application_handler::{ RadrootsNostrApplicationHandlerSpec, radroots_nostr_build_application_handler_event, + radroots_nostr_metadata_has_fields, }; #[cfg(all(feature = "client", feature = "events"))] @@ -78,6 +79,9 @@ pub mod prelude { radroots_nostr_publish_identity_profile, radroots_nostr_publish_identity_profile_with_type, }; + #[cfg(all(feature = "client", feature = "codec", feature = "events"))] + pub use crate::identity_profile::radroots_nostr_bootstrap_service_presence; + #[cfg(all(feature = "client", feature = "events"))] pub use crate::events::post::radroots_nostr_fetch_post_events;