From 6fc59bfe59207ec44e139a58e9191cbecbf2f838 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Tue, 6 May 2025 11:20:41 +0000 Subject: [PATCH] chore: Send the repo announce to the author relays list Signed-off-by: Awiteb --- src/cli/repo/announce.rs | 41 +++++++++++++++++++++-------------- src/error.rs | 2 ++ src/nostr_utils/mod.rs | 46 ++++++++++++++++++++++++++++------------ 3 files changed, 59 insertions(+), 30 deletions(-) diff --git a/src/cli/repo/announce.rs b/src/cli/repo/announce.rs index f6dbf69..afecec6 100644 --- a/src/cli/repo/announce.rs +++ b/src/cli/repo/announce.rs @@ -15,21 +15,20 @@ // along with this program. If not, see . use clap::Args; -use convert_case::{Case, Casing}; use nostr::{ - event::{EventBuilder, Kind, Tag}, + event::{EventBuilder, Kind}, key::PublicKey, nips::{ nip01::Coordinate, nip19::{Nip19Coordinate, Nip19Event, ToBech32}, - nip34::GitRepositoryAnnouncement, + nip65::{self, RelayMetadata}, }, types::Url, }; use crate::{ cli::{CliOptions, CommandRunner}, - error::{N34Error, N34Result}, + error::N34Result, nostr_utils::{NostrClient, traits::NewGitRepositoryAnnouncement}, }; @@ -63,28 +62,43 @@ pub struct AnnounceArgs { impl CommandRunner for AnnounceArgs { async fn run(self, options: CliOptions) -> N34Result<()> { let client = NostrClient::init(&options).await; + let user_pubk = options.pubkey().await; let naddr = Nip19Coordinate::new( - Coordinate::new(Kind::GitRepoAnnouncement, options.pubkey().await), + Coordinate::new(Kind::GitRepoAnnouncement, user_pubk), options.relays.iter().take(3), ) .expect("Valid relays"); + let relays_list = client.user_relays_list(user_pubk).await?; - let mut maintainers = vec![naddr.public_key]; + let mut write_relays = options.relays.clone(); + let mut maintainers = vec![user_pubk]; maintainers.extend(self.maintainers); - let event_builder = EventBuilder::new_git_repo( + if let Some(event) = relays_list.clone() { + write_relays.extend( + nip65::extract_owned_relay_list(event) + .filter_map(|(r, m)| m.is_none_or(|m| m == RelayMetadata::Write).then_some(r)), + ); + } + + let event = EventBuilder::new_git_repo( self.repo_id, self.name, self.description, self.web, self.clone, - options.relays.clone(), + options.relays, maintainers, self.labels, - )?; + )? + .build(user_pubk); + let nevent = Nip19Event::new(event.id.expect("There is an id")) + .relays(write_relays.iter().take(3).cloned()) + .to_bech32()?; + let result = client - .send_builder_to(event_builder, &options.relays) + .send_event_to(event, relays_list.as_ref(), write_relays) .await?; for relay in &result.success { @@ -94,12 +108,7 @@ impl CommandRunner for AnnounceArgs { tracing::warn!(relay = %relay, reason = %reason, "Failed to send event"); } - println!( - "Event: {}", - Nip19Event::new(result.val) - .relays(options.relays.into_iter().take(3)) - .to_bech32()? - ); + println!("Event: {nevent}",); println!("Repo Address: {}", naddr.to_bech32()?); Ok(()) diff --git a/src/error.rs b/src/error.rs index 9d80f93..9691f2a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -34,6 +34,8 @@ pub enum N34Error { InvalidRepoId, #[error("Bech32 error: {0}")] Bech32(#[from] nostr::nips::nip19::Error), + #[error("Event error: {0}")] + Event(#[from] nostr::event::Error), } impl N34Error { diff --git a/src/nostr_utils/mod.rs b/src/nostr_utils/mod.rs index efd33c7..25fb521 100644 --- a/src/nostr_utils/mod.rs +++ b/src/nostr_utils/mod.rs @@ -22,9 +22,9 @@ pub mod utils; use std::time::Duration; use nostr::{ - event::{EventBuilder, EventId, Kind}, + event::{Event, EventId, Kind, UnsignedEvent}, filter::Filter, - key::Keys, + key::{Keys, PublicKey}, nips::{nip19::Nip19Coordinate, nip34::GitRepositoryAnnouncement}, types::RelayUrl, }; @@ -35,6 +35,9 @@ use crate::{ error::{N34Error, N34Result}, }; +/// Timeout duration for the clinet. +const CLIENT_TIMEOUT: Duration = Duration::from_millis(1500); + /// A client for interacting with the Nostr relays pub struct NostrClient { /// The underlying Nostr client implementation @@ -73,28 +76,30 @@ impl NostrClient { .add_read_relay(relay) .await .expect("It's a valid relay url"); - if let Err(err) = self - .client - .try_connect_relay(relay, Duration::from_millis(1500)) - .await - { + if let Err(err) = self.client.try_connect_relay(relay, CLIENT_TIMEOUT).await { tracing::error!("Failed to connect to relay '{relay}': {err}"); } } } - /// Sends an event builder to the specified relays. - pub async fn send_builder_to( + /// Sends an event to the specified relays. + pub async fn send_event_to( &self, - builder: EventBuilder, - relays: &[RelayUrl], + event: UnsignedEvent, + relays_list: Option<&Event>, + mut relays: Vec, ) -> N34Result> { - for relay in relays { + relays.sort_unstable(); + relays.dedup(); + for relay in &relays { let _ = self.client.add_write_relay(relay).await; } + if let Some(event) = relays_list { + let _ = self.client.send_event_to(&relays, event).await; + } self.client - .send_event_builder_to(relays, builder) + .send_event_to(relays, &event.sign(&self.client.signer().await?).await?) .await .map_err(N34Error::from) } @@ -110,7 +115,7 @@ impl NostrClient { .identifier(&repo_naddr.identifier); let events = self .client - .fetch_events(filter, Duration::from_secs(1)) + .fetch_events(filter, CLIENT_TIMEOUT) .await .map_err(|_| N34Error::NotFoundRepo)?; @@ -120,4 +125,17 @@ impl NostrClient { &repo_naddr.identifier, )) } + + /// Fetches the relay list (kind 10002) for the given user. Returns None if + /// no relays are found. + pub async fn user_relays_list(&self, user: PublicKey) -> N34Result> { + Ok(self + .client + .fetch_events( + Filter::new().author(user).kind(Kind::RelayList), + CLIENT_TIMEOUT, + ) + .await? + .first_owned()) + } }