fix: import existing profile metadata
This commit is contained in:
parent
aacae4d2f6
commit
bde35bcec4
1 changed files with 106 additions and 0 deletions
106
src/profiles.rs
106
src/profiles.rs
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use zeroize::Zeroizing;
|
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.
|
/// The per-relay outcome of publishing a profile metadata event.
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct MetadataPublishReport {
|
pub struct MetadataPublishReport {
|
||||||
|
|
@ -414,6 +484,42 @@ fn publish_metadata_blocking(
|
||||||
.expect("metadata publish thread panicked")
|
.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(
|
async fn publish_metadata_async(
|
||||||
keys: &Keys,
|
keys: &Keys,
|
||||||
label: &str,
|
label: &str,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue