From cc87576cdfdd3aa18df6e94fbfa079d9e4f0241a Mon Sep 17 00:00:00 2001 From: Laszlo Megyer Date: Mon, 16 Dec 2024 11:38:06 +0100 Subject: feat(init): default to nip05 git nostr url If the user has NIP-05 set up in profile, and it resolves at the time of running `ngit init`, NIP-05 will be used in the nostr remote url. --- src/bin/ngit/sub_commands/init.rs | 81 ++++++++++++++++++++++++++++++++++----- src/lib/git/nostr_url.rs | 12 ++++-- src/lib/login/user.rs | 8 +++- src/lib/repo_ref.rs | 10 +++++ 4 files changed, 97 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/bin/ngit/sub_commands/init.rs b/src/bin/ngit/sub_commands/init.rs index 80e182b..f26eea3 100644 --- a/src/bin/ngit/sub_commands/init.rs +++ b/src/bin/ngit/sub_commands/init.rs @@ -1,9 +1,18 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use anyhow::{Context, Result}; -use console::Style; -use ngit::{cli_interactor::PromptConfirmParms, git::nostr_url::NostrUrlDecoded}; -use nostr::{FromBech32, PublicKey, ToBech32, nips::nip01::Coordinate}; +use console::{Style, Term}; +use ngit::{ + cli_interactor::PromptConfirmParms, + git::nostr_url::{NostrUrlDecoded, save_nip05_to_git_config_cache}, +}; +use nostr::{ + FromBech32, PublicKey, ToBech32, + nips::{ + nip01::Coordinate, + nip05::{self}, + }, +}; use nostr_sdk::{Kind, RelayUrl}; use crate::{ @@ -397,7 +406,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { println!("publishing repostory reference..."); - let repo_ref = RepoRef { + let mut repo_ref = RepoRef { identifier: identifier.clone(), name, description, @@ -408,6 +417,7 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { trusted_maintainer: user_ref.public_key, maintainers: maintainers.clone(), events: HashMap::new(), + nostr_git_url: None, }; let repo_event = repo_ref.to_event(&signer).await?; @@ -437,19 +447,72 @@ pub async fn launch(cli_args: &Cli, args: &SubCommandArgs) -> Result<()> { false, )?; - let relays = relays - .iter() - .map(std::string::ToString::to_string) - .collect::>(); + // if nip05 valid, set nostr git url to use that format + let hint_for_nip05_address = { + if let Some(nip05) = user_ref.metadata.nip05 { + let term = Term::stdout(); + term.write_line(&format!("fetching nip05 details for {nip05}..."))?; + if let Ok(nprofile) = nip05::profile(nip05.clone(), None).await { + let _ = term.clear_last_lines(1); + let _ = + save_nip05_to_git_config_cache(&nip05, &nprofile.public_key, &Some(&git_repo)); + // Normalize URLs before doing the intersection. + let repo_relays: HashSet = relays + .iter() + .map(|r| RelayUrl::parse(r.as_str_without_trailing_slash()).unwrap()) + .collect(); + let nip05_relays: HashSet = nprofile + .relays + .iter() + .map(|r| RelayUrl::parse(r.as_str_without_trailing_slash()).unwrap()) + .collect(); + let mut inter = repo_relays.intersection(&nip05_relays); + + repo_ref.set_nostr_git_url(NostrUrlDecoded { + original_string: String::new(), + nip05: Some(nip05.clone()), + coordinate: Coordinate { + kind: Kind::GitRepoAnnouncement, + public_key: user_ref.public_key, + identifier: repo_ref.identifier.clone(), + relays: if inter.next().is_some() || relays.is_empty() { + vec![] + } else { + vec![relays.first().unwrap().clone()] + }, + }, + protocol: None, + user: None, + }); + if inter.next().is_some() { + "note: point your NIP-05 relays to one of the repo relays for a cleaner nostr:// remote URL.".to_string() + } else { + String::new() + } + } else { + "note: could not validate your nip05 address {nip05} which could be used for a shorter nostr:// remote URL.".to_string() + } + } else { + String::new() + } + }; prompt_to_set_nostr_url_as_origin(&repo_ref, &git_repo).await?; + if !hint_for_nip05_address.is_empty() { + println!("{hint_for_nip05_address}"); + } + // TODO: if no state event exists and there is currently a remote called // "origin", automtically push rather than waiting for the next commit // no longer create a new maintainers.yaml file - its too confusing for users // as it falls out of sync with data in nostr event . update if it already // exists + let relays = relays + .iter() + .map(std::string::ToString::to_string) + .collect::>(); if match &repo_config_result { Ok(config) => { ! as Clone>::clone(&config.identifier) diff --git a/src/lib/git/nostr_url.rs b/src/lib/git/nostr_url.rs index 6b38a93..de7953f 100644 --- a/src/lib/git/nostr_url.rs +++ b/src/lib/git/nostr_url.rs @@ -55,7 +55,7 @@ impl FromStr for ServerProtocol { } } -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub struct NostrUrlDecoded { pub original_string: String, pub coordinate: Coordinate, @@ -73,7 +73,11 @@ impl fmt::Display for NostrUrlDecoded { if let Some(protocol) = &self.protocol { write!(f, "{}/", protocol)?; } - write!(f, "{}/", self.coordinate.public_key.to_bech32().unwrap())?; + if let Some(nip05) = &self.nip05 { + write!(f, "{}/", nip05)?; + } else { + write!(f, "{}/", self.coordinate.public_key.to_bech32().unwrap())?; + } if let Some(relay) = self.coordinate.relays.first() { write!( f, @@ -97,6 +101,7 @@ impl NostrUrlDecoded { let mut protocol = None; let mut user = None; let mut relays = vec![]; + let mut nip05 = None; if !url.starts_with("nostr://") { bail!("nostr git url must start with nostr://"); @@ -157,7 +162,6 @@ impl NostrUrlDecoded { } // extract naddr npub//identifer let part = parts.first().context(INCORRECT_NOSTR_URL_FORMAT_ERROR)?; - let mut nip05 = None; // naddr used let coordinate = if let Ok(coordinate) = Coordinate::parse(part) { if coordinate.kind.eq(&nostr_sdk::Kind::GitRepoAnnouncement) { @@ -254,7 +258,7 @@ pub fn use_nip05_git_config_cache_to_find_nip05_from_public_key( .cloned()) } -fn save_nip05_to_git_config_cache( +pub fn save_nip05_to_git_config_cache( nip05: &str, public_key: &PublicKey, git_repo: &Option<&Repo>, diff --git a/src/lib/login/user.rs b/src/lib/login/user.rs index 107e765..071cb25 100644 --- a/src/lib/login/user.rs +++ b/src/lib/login/user.rs @@ -22,6 +22,7 @@ pub struct UserRef { pub struct UserMetadata { pub name: String, pub created_at: Timestamp, + pub nip05: Option, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] @@ -149,7 +150,7 @@ pub fn extract_user_metadata( }; Ok(UserMetadata { - name: if let Some(metadata) = metadata { + name: if let Some(metadata) = metadata.clone() { if let Some(n) = metadata.name { n } else if let Some(n) = metadata.custom.get("displayName") { @@ -167,6 +168,11 @@ pub fn extract_user_metadata( } else { public_key.to_bech32()? }, + nip05: if let Some(metadata) = metadata { + metadata.nip05 + } else { + None + }, created_at: if let Some(event) = event { event.created_at } else { diff --git a/src/lib/repo_ref.rs b/src/lib/repo_ref.rs index a9d1186..8705597 100644 --- a/src/lib/repo_ref.rs +++ b/src/lib/repo_ref.rs @@ -38,6 +38,7 @@ pub struct RepoRef { pub maintainers: Vec, pub trusted_maintainer: PublicKey, pub events: HashMap, + pub nostr_git_url: Option, } impl TryFrom<(nostr::Event, Option)> for RepoRef { @@ -60,6 +61,7 @@ impl TryFrom<(nostr::Event, Option)> for RepoRef { maintainers: Vec::new(), trusted_maintainer: trusted_maintainer.unwrap_or(event.pubkey), events: HashMap::new(), + nostr_git_url: None, }; for tag in event.tags.iter() { @@ -235,7 +237,14 @@ impl RepoRef { .collect::)>>() } + pub fn set_nostr_git_url(&mut self, nostr_git_url: NostrUrlDecoded) { + self.nostr_git_url = Some(nostr_git_url) + } + pub fn to_nostr_git_url(&self, git_repo: &Option<&Repo>) -> String { + if let Some(nostr_git_url) = &self.nostr_git_url { + return nostr_git_url.original_string.clone(); + } let c = self.coordinate_with_hint(); format!("{}", NostrUrlDecoded { original_string: String::new(), @@ -550,6 +559,7 @@ mod tests { trusted_maintainer: TEST_KEY_1_KEYS.public_key(), maintainers: vec![TEST_KEY_1_KEYS.public_key(), TEST_KEY_2_KEYS.public_key()], events: HashMap::new(), + nostr_git_url: None, } .to_event(&TEST_KEY_1_SIGNER) .await -- cgit v1.2.3