From 9845dd025d9b753787c5fedbb5a49169db920d3a Mon Sep 17 00:00:00 2001 From: Bytemalte Date: Sat, 31 Jan 2026 16:27:12 +0100 Subject: [PATCH] added metadata and test --- README.md | 19 ++++++++++++ src/bin/testall.rs | 20 ++++++++++++ src/functions/metadata.rs | 64 +++++++++++++++++++++++++++++++++++++++ src/functions/mod.rs | 1 + src/lib.rs | 30 +++++++++++++++++- src/nips/nip01.rs | 45 +++++++++++++++++++++++++++ 6 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 src/functions/metadata.rs diff --git a/README.md b/README.md index f257c04..40eb3c9 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ * **Social Connectivity**: Follow users and manage contact lists. * **Rich Feeds**: Access follow-based timelines, global discovery, and hashtag-based searches. * **Encrypted Messaging**: Secure NIP-17 direct messages. +* **Profile Management**: Update account metadata (Kind 0) with support for standard and custom fields. * **Extensible Architecture**: Clean separation between protocol logic (NIPs) and high-level functions. --- @@ -67,6 +68,24 @@ let event_id = en.post_text("Building with EasyNostr is awesome!").await?; println!("Successfully posted! Event ID: {}", event_id); ``` +### 👤 Profile & Metadata + +Update account information (NIP-01) including support for custom fields (e.g., bot, birthday). + +```rust +en.set_metadata( + Some("user_name".to_string()), + Some("Display Name".to_string()), + Some("About me...".to_string()), + Some("user@domain.com".to_string()), // NIP-05 + Some("user@getalby.com".to_string()), // LUD-16 + Some("https://website.com".to_string()), + Some("https://banner.com/img.png".to_string()), + Some(false), // bot (custom field) + Some("1990-01-01".to_string()), // birthday (custom field) +).await?; +``` + ### 📰 Social Feeds #### Timeline (Followed Users) diff --git a/src/bin/testall.rs b/src/bin/testall.rs index 4aea0d1..65116d7 100644 --- a/src/bin/testall.rs +++ b/src/bin/testall.rs @@ -112,6 +112,26 @@ async fn main() -> anyhow::Result<()> { println!(" [WARN] No posts found for tags (depends on relay traffic)."); } + // 10. Metadata Update (Kind 0) + println!("\n[STEP 8] Testing Profile Metadata Update..."); + let metadata_id = ez + .set_metadata( + Some("EasyNostr Test Bot".to_string()), + Some("EasyNostr".to_string()), + Some("Automated test for metadata updates".to_string()), + None, // NIP-05 + None, // LUD-16 + Some("https://github.com/Bytemalte/easy-nostr".to_string()), + Some( + "https://raw.githubusercontent.com/Bytemalte/easy-nostr/main/banner.png" + .to_string(), + ), + Some(true), // bot + Some("2024-01-31".to_string()), // birthday + ) + .await?; + println!(" [OK] Metadata updated: {}", metadata_id.to_bech32()?); + println!("\n=== Test Complete ==="); Ok(()) } diff --git a/src/functions/metadata.rs b/src/functions/metadata.rs new file mode 100644 index 0000000..9ffcca4 --- /dev/null +++ b/src/functions/metadata.rs @@ -0,0 +1,64 @@ +use crate::nips::nip01; +use anyhow::{Result, bail}; +use nostr_sdk::prelude::*; + +/// Updates the metadata of the current account. +/// +/// # Arguments +/// * `client` - The Nostr client. +/// * `name` - Optional name. +/// * `display_name` - Optional display name. +/// * `about` - Optional about description. +/// * `nip05` - Optional NIP-05 identifier. +/// * `lud16` - Optional LDP-16 (Lightning Address). +/// * `website` - Optional website URL. +/// * `banner` - Optional banner image URL. +/// * `bot` - Optional boolean identifying if the account is a bot. +/// * `birthday` - Optional birthday string. +pub async fn set_metadata( + client: &Client, + name: Option, + display_name: Option, + about: Option, + nip05: Option, + lud16: Option, + website: Option, + banner: Option, + bot: Option, + birthday: Option, +) -> Result { + // Basic validation for NIP-05 and LUD-16 + if let Some(ref n05) = nip05 { + if !n05.contains('@') { + bail!("Invalid NIP-05 format: must contain '@'"); + } + } + + if let Some(ref l16) = lud16 { + if !l16.contains('@') { + bail!("Invalid LUD-16 format: must contain '@'"); + } + } + + // Parse URLs if provided + let website_url = website.map(|w| Url::parse(&w)).transpose()?; + let banner_url = banner.map(|b| Url::parse(&b)).transpose()?; + + // Delegate creation of metadata struct to nip01 + let metadata = nip01::create_metadata( + name, + display_name, + about, + nip05, + lud16, + website_url, + banner_url, + bot, + birthday, + ); + + // Send the metadata event (Kind 0) + let event_id = client.set_metadata(&metadata).await?; + + Ok(*event_id) +} diff --git a/src/functions/mod.rs b/src/functions/mod.rs index 7caf6cd..708219d 100644 --- a/src/functions/mod.rs +++ b/src/functions/mod.rs @@ -3,6 +3,7 @@ pub mod feed; pub mod get_contacts; pub mod hashtags; pub mod messages; +pub mod metadata; pub mod new; pub mod publish; pub mod relays; diff --git a/src/lib.rs b/src/lib.rs index a6fb6a7..843827d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,7 @@ use nostr_sdk::prelude::*; // Wir importieren die Funktionen, um Tipparbeit zu sparen use crate::functions::{ - create_contact, feed, get_contacts, hashtags, messages, new, publish, relays, + create_contact, feed, get_contacts, hashtags, messages, metadata, new, publish, relays, }; use crate::nips::nip01::Post; use crate::nips::nip17::Message; @@ -70,4 +70,32 @@ impl EasyNostr { pub async fn get_posts_by_hashtags(&self, hashtags: Vec) -> Result> { hashtags::get_posts_by_hashtags(&self.client, hashtags).await } + + /// Aktualisiert das Profil-Metadaten (Kind 0) + pub async fn set_metadata( + &self, + name: Option, + display_name: Option, + about: Option, + nip05: Option, + lud16: Option, + website: Option, + banner: Option, + bot: Option, + birthday: Option, + ) -> Result { + metadata::set_metadata( + &self.client, + name, + display_name, + about, + nip05, + lud16, + website, + banner, + bot, + birthday, + ) + .await + } } diff --git a/src/nips/nip01.rs b/src/nips/nip01.rs index 763b75e..5283b93 100644 --- a/src/nips/nip01.rs +++ b/src/nips/nip01.rs @@ -17,3 +17,48 @@ pub async fn publish_text(client: &Client, content: &str) -> Result { let output = client.send_event_builder(builder).await?; Ok(*output.id()) } + +/// Creates a Metadata object for updating profile (Kind 0) +pub fn create_metadata( + name: Option, + display_name: Option, + about: Option, + nip05: Option, + lud16: Option, + website: Option, + banner: Option, + bot: Option, + birthday: Option, +) -> Metadata { + let mut metadata = Metadata::new(); + + if let Some(val) = name { + metadata = metadata.name(val); + } + if let Some(val) = display_name { + metadata = metadata.display_name(val); + } + if let Some(val) = about { + metadata = metadata.about(val); + } + if let Some(val) = nip05 { + metadata = metadata.nip05(val); + } + if let Some(val) = lud16 { + metadata = metadata.lud16(val); + } + if let Some(val) = website { + metadata = metadata.website(val); + } + if let Some(val) = banner { + metadata = metadata.banner(val); + } + if let Some(val) = bot { + metadata = metadata.custom_field("bot", val); + } + if let Some(val) = birthday { + metadata = metadata.custom_field("birthday", val); + } + + metadata +}