fix: import existing profile metadata

This commit is contained in:
Avi 2026-09-01 09:26:20 -05:00
commit bde35bcec4

View file

@ -1,3 +1,5 @@
use std::time::Duration;
use nostr_sdk::prelude::*;
use serde::Serialize;
use zeroize::Zeroizing;
@ -99,6 +101,74 @@ pub fn create_profile(
})
}
/// Add an existing profile from a hex or bech32 secret key.
pub fn import_profile(
vault: &mut Vault,
label: String,
secret: &str,
key: Option<&VaultKey>,
settings: &Settings,
) -> Result<ProfileSummary, AppError> {
if vault.is_encrypted() && key.is_none() {
return Err(AppError::vault_locked());
}
let secret_key = SecretKey::from_bech32(secret.trim())
.or_else(|_| parse_secret_key(secret.trim()))
.map_err(|e| AppError::invalid_secret(format!("Could not import that secret key: {e}")))?;
let keys = Keys::new(secret_key);
let public_key = keys
.public_key()
.to_bech32()
.map_err(|e| AppError::internal(format!("Could not encode the public key: {e}")))?;
if vault
.profiles
.iter()
.any(|profile| profile.public_key == public_key)
{
return Err(AppError::config("This account is already added."));
}
let secret_hex = Zeroizing::new(hex::encode(keys.secret_key().to_secret_bytes()));
let stored_secret = match &vault.crypto {
Some(_) => crate::crypto::encrypt_secret(key.expect("guarded above"), &secret_hex)?,
None => secret_hex.to_string(),
};
let created_at = unix_timestamp()?;
let metadata = fetch_profile_metadata(&public_key, &relays::enabled_urls(settings));
let is_active = vault.active_profile.is_none();
if is_active {
vault.active_profile = Some(public_key.clone());
}
vault.profiles.push(StoredProfile {
label: label.clone(),
public_key: public_key.clone(),
secret_key: stored_secret,
created_at,
picture: metadata.as_ref().and_then(|m| m.picture.clone()),
nip05: metadata.as_ref().and_then(|m| m.nip05.clone()),
});
let relay_urls = relays::enabled_urls(settings);
if !relay_urls.is_empty() {
publish_metadata_blocking(
&keys,
&label,
metadata.as_ref().and_then(|m| m.picture.clone()),
metadata.as_ref().and_then(|m| m.nip05.clone()),
relay_urls,
);
}
Ok(ProfileSummary {
label,
npub: public_key,
created_at,
is_active,
picture: metadata.as_ref().and_then(|m| m.picture.clone()),
nip05: metadata.and_then(|m| m.nip05),
})
}
/// The per-relay outcome of publishing a profile metadata event.
#[derive(Debug, Clone, Serialize)]
pub struct MetadataPublishReport {
@ -414,6 +484,42 @@ fn publish_metadata_blocking(
.expect("metadata publish thread panicked")
}
/// Best-effort lookup of the account's latest kind-0 metadata. Import must
/// still succeed when relays are unavailable, so lookup failures are ignored.
fn fetch_profile_metadata(npub: &str, relay_urls: &[String]) -> Option<Metadata> {
if relay_urls.is_empty() {
return None;
}
let public_key = PublicKey::from_bech32(npub).ok()?;
let relay_urls = relay_urls.to_vec();
std::thread::spawn(move || {
tokio::runtime::Runtime::new().ok()?.block_on(async move {
let client =
relays::open_pool(Keys::generate(), &relay_urls, Some(Duration::from_secs(10)))
.await
.ok()?;
let events = client
.fetch_events(
Filter::new()
.kind(Kind::Metadata)
.author(public_key)
.limit(1),
)
.timeout(Duration::from_secs(10))
.await
.ok()?;
client.disconnect().await;
events
.into_iter()
.max_by_key(|event| event.created_at)
.and_then(|event| serde_json::from_str::<Metadata>(&event.content).ok())
})
})
.join()
.ok()
.flatten()
}
async fn publish_metadata_async(
keys: &Keys,
label: &str,