Skip to main content

am_core/
profile.rs

1use nostr_sdk::prelude::*;
2use serde::Serialize;
3
4use crate::client::{self, RelayResult};
5use crate::config::load_config;
6use crate::error::{AmError, AmResult};
7use crate::identity::load_keys;
8
9#[derive(Debug, Serialize)]
10pub struct ProfileInfo {
11    pub npub: String,
12    #[serde(skip_serializing_if = "Option::is_none")]
13    pub name: Option<String>,
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub about: Option<String>,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub picture: Option<String>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub website: Option<String>,
20    pub event_id: String,
21    #[serde(skip_serializing_if = "Vec::is_empty")]
22    pub relays: Vec<RelayResult>,
23}
24
25pub async fn set(
26    identity: Option<&str>,
27    name: Option<&str>,
28    about: Option<&str>,
29    picture: Option<&str>,
30    website: Option<&str>,
31    passphrase: Option<&str>,
32    verbosity: u8,
33) -> AmResult<ProfileInfo> {
34    if name.is_none() && about.is_none() && picture.is_none() && website.is_none() {
35        return Err(AmError::Args(
36            "at least one of --name, --about, --picture, or --website is required".into(),
37        ));
38    }
39
40    let keys = load_keys(identity.unwrap_or("default"), passphrase)?;
41    let config = load_config()?;
42
43    if config.relays.is_empty() {
44        return Err(AmError::Network(
45            "no relays configured; run `am relay add <url>`".into(),
46        ));
47    }
48
49    let mut metadata = Metadata::new();
50    if let Some(n) = name {
51        metadata.name = Some(n.to_string());
52    }
53    if let Some(a) = about {
54        metadata.about = Some(a.to_string());
55    }
56    if let Some(p) = picture {
57        metadata.picture = Some(p.to_string());
58    }
59    if let Some(w) = website {
60        metadata.website = Some(w.to_string());
61    }
62
63    let npub = keys.public_key().to_bech32().unwrap_or_default();
64    let client = client::connect(keys, &config.relays).await?;
65
66    // Build and sign the metadata event, then send with retry
67    let event = client
68        .sign_event_builder(EventBuilder::metadata(&metadata))
69        .await
70        .map_err(|e| AmError::Network(e.to_string()))?;
71
72    let event_id = event.id.to_bech32().unwrap_or_default();
73
74    let (relay_results, _succeeded) =
75        client::send_with_retry(&client, &event, &config.relays, 3, verbosity).await;
76
77    client.disconnect().await;
78
79    Ok(ProfileInfo {
80        npub,
81        name: name.map(String::from),
82        about: about.map(String::from),
83        picture: picture.map(String::from),
84        website: website.map(String::from),
85        event_id,
86        relays: relay_results,
87    })
88}