lib

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

commit 1f7704a69b4421c1fa543e5f77b6c94bfdd2dec6
parent 0c2fe0fc1da81aeee83621510d9e1d4c9e6da4a1
Author: triesap <tyson@radroots.org>
Date:   Wed,  7 Jan 2026 17:14:12 +0000

events: add NIP-89 application handler


- add application handler spec and builder
- publish handler events via client helper
- include metadata content when present
- re-export helpers through nostr prelude

Diffstat:
Anostr/src/events/application_handler.rs | 88+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mnostr/src/events/mod.rs | 2++
Mnostr/src/lib.rs | 9+++++++++
3 files changed, 99 insertions(+), 0 deletions(-)

diff --git a/nostr/src/events/application_handler.rs b/nostr/src/events/application_handler.rs @@ -0,0 +1,88 @@ +#![forbid(unsafe_code)] + +extern crate alloc; + +use alloc::{string::String, vec::Vec}; + +use crate::error::RadrootsNostrError; +use crate::events::radroots_nostr_build_event; +use crate::types::{RadrootsNostrEventBuilder, RadrootsNostrMetadata}; +use radroots_events::kinds::KIND_APPLICATION_HANDLER; + +#[derive(Debug, Clone)] +pub struct RadrootsNostrApplicationHandlerSpec { + pub kinds: Vec<u32>, + pub identifier: Option<String>, + pub metadata: Option<RadrootsNostrMetadata>, + pub extra_tags: Vec<Vec<String>>, +} + +impl RadrootsNostrApplicationHandlerSpec { + pub fn new(kinds: Vec<u32>) -> Self { + Self { + kinds, + identifier: None, + metadata: None, + extra_tags: Vec::new(), + } + } +} + +pub fn radroots_nostr_build_application_handler_event( + spec: &RadrootsNostrApplicationHandlerSpec, +) -> Result<RadrootsNostrEventBuilder, RadrootsNostrError> { + if spec.kinds.is_empty() { + return Err(RadrootsNostrError::FilterTagError( + "application handler kinds are empty".to_string(), + )); + } + + let identifier = spec + .identifier + .clone() + .unwrap_or_else(|| spec.kinds[0].to_string()); + + let mut content = String::new(); + if let Some(md) = spec.metadata.as_ref() { + if metadata_has_fields(md) { + content = serde_json::to_string(md).unwrap_or_default(); + } + } + + let mut tags = Vec::new(); + tags.push(vec!["d".to_string(), identifier]); + for kind in &spec.kinds { + tags.push(vec!["k".to_string(), kind.to_string()]); + } + for tag in &spec.extra_tags { + if tag.is_empty() { + continue; + } + tags.push(tag.clone()); + } + + radroots_nostr_build_event(KIND_APPLICATION_HANDLER, content, tags) +} + +fn metadata_has_fields(md: &RadrootsNostrMetadata) -> bool { + md.name.is_some() + || md.display_name.is_some() + || md.about.is_some() + || md.website.is_some() + || md.picture.is_some() + || md.banner.is_some() + || md.nip05.is_some() + || md.lud06.is_some() + || md.lud16.is_some() + || !md.custom.is_empty() +} + +#[cfg(feature = "client")] +pub async fn radroots_nostr_publish_application_handler( + client: &crate::client::RadrootsNostrClient, + spec: &RadrootsNostrApplicationHandlerSpec, +) -> Result<crate::types::RadrootsNostrOutput<crate::types::RadrootsNostrEventId>, RadrootsNostrError> +{ + let builder = radroots_nostr_build_application_handler_event(spec)?; + crate::client::radroots_nostr_send_event(client, builder).await +} diff --git a/nostr/src/events/mod.rs b/nostr/src/events/mod.rs @@ -1,6 +1,8 @@ pub mod jobs; pub mod metadata; pub mod post; +#[cfg(feature = "events")] +pub mod application_handler; extern crate alloc; use alloc::{string::String, vec::Vec}; diff --git a/nostr/src/lib.rs b/nostr/src/lib.rs @@ -68,6 +68,15 @@ pub mod prelude { }, }; + #[cfg(feature = "events")] + pub use crate::events::application_handler::{ + radroots_nostr_build_application_handler_event, + RadrootsNostrApplicationHandlerSpec, + }; + + #[cfg(all(feature = "client", feature = "events"))] + pub use crate::events::application_handler::radroots_nostr_publish_application_handler; + #[cfg(feature = "client")] pub use crate::events::metadata::{ radroots_nostr_fetch_metadata_for_author,