diff options
Diffstat (limited to 'src/lib/login/user.rs')
| -rw-r--r-- | src/lib/login/user.rs | 155 |
1 files changed, 154 insertions, 1 deletions
diff --git a/src/lib/login/user.rs b/src/lib/login/user.rs index 46652db..4456308 100644 --- a/src/lib/login/user.rs +++ b/src/lib/login/user.rs | |||
| @@ -1,7 +1,16 @@ | |||
| 1 | use std::{collections::HashSet, path::Path}; | ||
| 2 | |||
| 3 | use anyhow::{bail, Context, Result}; | ||
| 1 | use nostr::PublicKey; | 4 | use nostr::PublicKey; |
| 2 | use nostr_sdk::Timestamp; | 5 | use nostr_sdk::{Alphabet, JsonUtil, Kind, SingleLetterTag, Timestamp, ToBech32}; |
| 3 | use serde::{self, Deserialize, Serialize}; | 6 | use serde::{self, Deserialize, Serialize}; |
| 4 | 7 | ||
| 8 | #[cfg(not(test))] | ||
| 9 | use crate::client::Client; | ||
| 10 | #[cfg(test)] | ||
| 11 | use crate::client::MockConnect; | ||
| 12 | use crate::client::{get_event_from_global_cache, Connect}; | ||
| 13 | |||
| 5 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] | 14 | #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] |
| 6 | pub struct UserRef { | 15 | pub struct UserRef { |
| 7 | pub public_key: PublicKey, | 16 | pub public_key: PublicKey, |
| @@ -37,3 +46,147 @@ pub struct UserRelayRef { | |||
| 37 | pub read: bool, | 46 | pub read: bool, |
| 38 | pub write: bool, | 47 | pub write: bool, |
| 39 | } | 48 | } |
| 49 | |||
| 50 | pub async fn get_user_details( | ||
| 51 | public_key: &PublicKey, | ||
| 52 | #[cfg(test)] client: Option<&MockConnect>, | ||
| 53 | #[cfg(not(test))] client: Option<&Client>, | ||
| 54 | git_repo_path: Option<&Path>, | ||
| 55 | cache_only: bool, | ||
| 56 | ) -> Result<UserRef> { | ||
| 57 | if let Ok(user_ref) = get_user_ref_from_cache(git_repo_path, public_key).await { | ||
| 58 | Ok(user_ref) | ||
| 59 | } else { | ||
| 60 | let empty = UserRef { | ||
| 61 | public_key: public_key.to_owned(), | ||
| 62 | metadata: extract_user_metadata(public_key, &[])?, | ||
| 63 | relays: extract_user_relays(public_key, &[]), | ||
| 64 | }; | ||
| 65 | if cache_only { | ||
| 66 | Ok(empty) | ||
| 67 | } else if let Some(client) = client { | ||
| 68 | let term = console::Term::stderr(); | ||
| 69 | term.write_line("searching for profile...")?; | ||
| 70 | let (_, progress_reporter) = client | ||
| 71 | .fetch_all( | ||
| 72 | git_repo_path, | ||
| 73 | &HashSet::new(), | ||
| 74 | &HashSet::from_iter(vec![*public_key]), | ||
| 75 | ) | ||
| 76 | .await?; | ||
| 77 | if let Ok(user_ref) = get_user_ref_from_cache(git_repo_path, public_key).await { | ||
| 78 | progress_reporter.clear()?; | ||
| 79 | // if std::env::var("NGITTEST").is_err() {term.clear_last_lines(1)?;} | ||
| 80 | Ok(user_ref) | ||
| 81 | } else { | ||
| 82 | Ok(empty) | ||
| 83 | } | ||
| 84 | } else { | ||
| 85 | Ok(empty) | ||
| 86 | } | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | pub async fn get_user_ref_from_cache( | ||
| 91 | git_repo_path: Option<&Path>, | ||
| 92 | public_key: &PublicKey, | ||
| 93 | ) -> Result<UserRef> { | ||
| 94 | let filters = vec![ | ||
| 95 | nostr::Filter::default() | ||
| 96 | .author(*public_key) | ||
| 97 | .kind(Kind::Metadata), | ||
| 98 | nostr::Filter::default() | ||
| 99 | .author(*public_key) | ||
| 100 | .kind(Kind::RelayList), | ||
| 101 | ]; | ||
| 102 | |||
| 103 | let events = get_event_from_global_cache(git_repo_path, filters.clone()).await?; | ||
| 104 | |||
| 105 | if events.is_empty() { | ||
| 106 | bail!("no metadata and profile list in cache for selected public key"); | ||
| 107 | } | ||
| 108 | Ok(UserRef { | ||
| 109 | public_key: public_key.to_owned(), | ||
| 110 | metadata: extract_user_metadata(public_key, &events)?, | ||
| 111 | relays: extract_user_relays(public_key, &events), | ||
| 112 | }) | ||
| 113 | } | ||
| 114 | |||
| 115 | pub fn extract_user_metadata( | ||
| 116 | public_key: &nostr::PublicKey, | ||
| 117 | events: &[nostr::Event], | ||
| 118 | ) -> Result<UserMetadata> { | ||
| 119 | let event = events | ||
| 120 | .iter() | ||
| 121 | .filter(|e| e.kind.eq(&nostr::Kind::Metadata) && e.pubkey.eq(public_key)) | ||
| 122 | .max_by_key(|e| e.created_at); | ||
| 123 | |||
| 124 | let metadata: Option<nostr::Metadata> = if let Some(event) = event { | ||
| 125 | Some( | ||
| 126 | nostr::Metadata::from_json(event.content.clone()) | ||
| 127 | .context("metadata cannot be found in kind 0 event content")?, | ||
| 128 | ) | ||
| 129 | } else { | ||
| 130 | None | ||
| 131 | }; | ||
| 132 | |||
| 133 | Ok(UserMetadata { | ||
| 134 | name: if let Some(metadata) = metadata { | ||
| 135 | if let Some(n) = metadata.name { | ||
| 136 | n | ||
| 137 | } else if let Some(n) = metadata.custom.get("displayName") { | ||
| 138 | // strip quote marks that custom.get() adds | ||
| 139 | let binding = n.to_string(); | ||
| 140 | let mut chars = binding.chars(); | ||
| 141 | chars.next(); | ||
| 142 | chars.next_back(); | ||
| 143 | chars.as_str().to_string() | ||
| 144 | } else if let Some(n) = metadata.display_name { | ||
| 145 | n | ||
| 146 | } else { | ||
| 147 | public_key.to_bech32()? | ||
| 148 | } | ||
| 149 | } else { | ||
| 150 | public_key.to_bech32()? | ||
| 151 | }, | ||
| 152 | created_at: if let Some(event) = event { | ||
| 153 | event.created_at | ||
| 154 | } else { | ||
| 155 | Timestamp::from(0) | ||
| 156 | }, | ||
| 157 | }) | ||
| 158 | } | ||
| 159 | |||
| 160 | pub fn extract_user_relays(public_key: &nostr::PublicKey, events: &[nostr::Event]) -> UserRelays { | ||
| 161 | let event = events | ||
| 162 | .iter() | ||
| 163 | .filter(|e| e.kind.eq(&nostr::Kind::RelayList) && e.pubkey.eq(public_key)) | ||
| 164 | .max_by_key(|e| e.created_at); | ||
| 165 | |||
| 166 | UserRelays { | ||
| 167 | relays: if let Some(event) = event { | ||
| 168 | event | ||
| 169 | .tags | ||
| 170 | .iter() | ||
| 171 | .filter(|t| { | ||
| 172 | t.kind() | ||
| 173 | .eq(&nostr::TagKind::SingleLetter(SingleLetterTag::lowercase( | ||
| 174 | Alphabet::R, | ||
| 175 | ))) | ||
| 176 | }) | ||
| 177 | .map(|t| UserRelayRef { | ||
| 178 | url: t.as_slice()[1].clone(), | ||
| 179 | read: t.as_slice().len() == 2 || t.as_slice()[2].eq("read"), | ||
| 180 | write: t.as_slice().len() == 2 || t.as_slice()[2].eq("write"), | ||
| 181 | }) | ||
| 182 | .collect() | ||
| 183 | } else { | ||
| 184 | vec![] | ||
| 185 | }, | ||
| 186 | created_at: if let Some(event) = event { | ||
| 187 | event.created_at | ||
| 188 | } else { | ||
| 189 | Timestamp::from(0) | ||
| 190 | }, | ||
| 191 | } | ||
| 192 | } | ||