nostr.rs (12132B)
1 use super::RadrootsRuntime; 2 use crate::RadrootsAppError; 3 #[cfg(feature = "nostr-client")] 4 use tokio::sync::broadcast::error::TryRecvError; 5 6 #[cfg(feature = "nostr-client")] 7 fn nostr_manager( 8 runtime: &RadrootsRuntime, 9 ) -> Result<radroots_net_core::nostr_client::NostrClientManager, RadrootsAppError> { 10 let guard = runtime 11 .net 12 .lock() 13 .map_err(|err| RadrootsAppError::runtime(format!("{err}")))?; 14 guard 15 .nostr 16 .clone() 17 .ok_or_else(|| RadrootsAppError::relay("nostr not initialized")) 18 } 19 20 #[derive(uniffi::Enum, Debug, Clone, Copy, PartialEq, Eq)] 21 pub enum NostrLight { 22 Red, 23 Yellow, 24 Green, 25 } 26 27 #[derive(uniffi::Record, Debug, Clone)] 28 pub struct NostrConnectionStatus { 29 pub light: NostrLight, 30 pub connected: u32, 31 pub connecting: u32, 32 pub last_error: Option<String>, 33 } 34 35 #[derive(uniffi::Record, Debug, Clone, Default)] 36 pub struct NostrProfile { 37 pub name: Option<String>, 38 pub display_name: Option<String>, 39 pub nip05: Option<String>, 40 pub about: Option<String>, 41 pub website: Option<String>, 42 pub picture: Option<String>, 43 pub banner: Option<String>, 44 pub lud06: Option<String>, 45 pub lud16: Option<String>, 46 pub bot: Option<String>, 47 } 48 49 #[derive(uniffi::Record, Debug, Clone)] 50 pub struct NostrProfileEventMetadata { 51 pub id: String, 52 pub author: String, 53 pub published_at: u64, 54 pub profile: NostrProfile, 55 } 56 57 #[derive(uniffi::Record, Debug, Clone)] 58 pub struct NostrEvent { 59 pub id: String, 60 pub author: String, 61 pub created_at: u64, 62 pub kind: u32, 63 pub content: String, 64 } 65 66 #[derive(uniffi::Record, Debug, Clone)] 67 pub struct NostrPost { 68 pub content: String, 69 } 70 71 #[derive(uniffi::Record, Debug, Clone)] 72 pub struct NostrPostEventMetadata { 73 pub id: String, 74 pub author: String, 75 pub published_at: u64, 76 pub post: NostrPost, 77 } 78 79 #[cfg(feature = "nostr-client")] 80 fn map_post_event_metadata( 81 event: radroots_events_codec::parsed::RadrootsParsedData<radroots_events::post::RadrootsPost>, 82 ) -> NostrPostEventMetadata { 83 NostrPostEventMetadata { 84 id: event.id, 85 author: event.author, 86 published_at: event.published_at as u64, 87 post: NostrPost { 88 content: event.data.content, 89 }, 90 } 91 } 92 93 #[cfg(feature = "nostr-client")] 94 fn map_profile_event_metadata( 95 event: radroots_events_codec::parsed::RadrootsParsedData< 96 radroots_events_codec::profile::RadrootsProfileData, 97 >, 98 ) -> NostrProfileEventMetadata { 99 NostrProfileEventMetadata { 100 id: event.id, 101 author: event.author, 102 published_at: event.published_at as u64, 103 profile: NostrProfile { 104 name: event.data.profile.name.into(), 105 display_name: event.data.profile.display_name.into(), 106 nip05: event.data.profile.nip05.into(), 107 about: event.data.profile.about.into(), 108 website: event.data.profile.website, 109 picture: event.data.profile.picture, 110 banner: event.data.profile.banner, 111 lud06: event.data.profile.lud06, 112 lud16: event.data.profile.lud16, 113 bot: event.data.profile.bot, 114 }, 115 } 116 } 117 118 #[cfg_attr(not(coverage_nightly), uniffi::export)] 119 impl RadrootsRuntime { 120 pub fn nostr_set_default_relays(&self, relays: Vec<String>) -> Result<(), RadrootsAppError> { 121 #[cfg(feature = "nostr-client")] 122 { 123 let mut guard = match self.net.lock() { 124 Ok(guard) => guard, 125 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), 126 }; 127 guard 128 .nostr_set_default_relays(&relays) 129 .map_err(|e| RadrootsAppError::relay(format!("{e}"))) 130 } 131 #[cfg(not(feature = "nostr-client"))] 132 { 133 let _ = relays; 134 Err(RadrootsAppError::unsupported("nostr disabled")) 135 } 136 } 137 138 pub fn nostr_connect_if_key_present(&self) -> Result<(), RadrootsAppError> { 139 #[cfg(feature = "nostr-client")] 140 { 141 let mut guard = match self.net.lock() { 142 Ok(guard) => guard, 143 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), 144 }; 145 guard 146 .nostr_connect_if_key_present() 147 .map_err(|e| RadrootsAppError::relay(format!("{e}"))) 148 } 149 #[cfg(not(feature = "nostr-client"))] 150 { 151 Err(RadrootsAppError::unsupported("nostr disabled")) 152 } 153 } 154 155 pub fn nostr_connection_status(&self) -> NostrConnectionStatus { 156 #[cfg(feature = "nostr-client")] 157 { 158 let guard = self.net.lock(); 159 if let Ok(g) = guard { 160 if let Some(s) = g.nostr_connection_snapshot() { 161 let light = match s.light { 162 radroots_net_core::nostr_client::Light::Green => NostrLight::Green, 163 radroots_net_core::nostr_client::Light::Yellow => NostrLight::Yellow, 164 radroots_net_core::nostr_client::Light::Red => NostrLight::Red, 165 }; 166 return NostrConnectionStatus { 167 light, 168 connected: s.connected as u32, 169 connecting: s.connecting as u32, 170 last_error: s.last_error, 171 }; 172 } 173 } 174 NostrConnectionStatus { 175 light: NostrLight::Red, 176 connected: 0, 177 connecting: 0, 178 last_error: None, 179 } 180 } 181 182 #[cfg(not(feature = "nostr-client"))] 183 { 184 NostrConnectionStatus { 185 light: NostrLight::Red, 186 connected: 0, 187 connecting: 0, 188 last_error: None, 189 } 190 } 191 } 192 193 pub fn nostr_profile_for_self( 194 &self, 195 ) -> Result<Option<NostrProfileEventMetadata>, RadrootsAppError> { 196 #[cfg(feature = "nostr-client")] 197 { 198 let (pk, mgr) = { 199 let guard = self 200 .net 201 .lock() 202 .map_err(|err| RadrootsAppError::runtime(format!("{err}")))?; 203 let keys = guard.selected_nostr_keys().ok_or_else(|| { 204 RadrootsAppError::identity("selected signing identity is not configured") 205 })?; 206 let pk = keys.public_key(); 207 let mgr = guard 208 .nostr 209 .clone() 210 .ok_or_else(|| RadrootsAppError::relay("nostr not initialized"))?; 211 (pk, mgr) 212 }; 213 let out = mgr 214 .fetch_profile_event_blocking(pk) 215 .map_err(|error| RadrootsAppError::relay(error.to_string()))?; 216 Ok(out.map(map_profile_event_metadata)) 217 } 218 #[cfg(not(feature = "nostr-client"))] 219 { 220 Err(RadrootsAppError::unsupported("nostr disabled")) 221 } 222 } 223 224 pub fn nostr_post_profile( 225 &self, 226 name: Option<String>, 227 display_name: Option<String>, 228 nip05: Option<String>, 229 about: Option<String>, 230 ) -> Result<String, RadrootsAppError> { 231 #[cfg(feature = "nostr-client")] 232 { 233 let mgr = nostr_manager(self)?; 234 mgr.publish_profile_event_blocking(name, display_name, nip05, about) 235 .map_err(|e| RadrootsAppError::relay(e.to_string())) 236 } 237 #[cfg(not(feature = "nostr-client"))] 238 { 239 let _ = (name, display_name, nip05, about); 240 Err(RadrootsAppError::unsupported("nostr disabled")) 241 } 242 } 243 244 pub fn nostr_post_text_note(&self, content: String) -> Result<String, RadrootsAppError> { 245 #[cfg(feature = "nostr-client")] 246 { 247 let mgr = nostr_manager(self)?; 248 mgr.publish_post_event_blocking(content) 249 .map_err(|e| RadrootsAppError::relay(e.to_string())) 250 } 251 #[cfg(not(feature = "nostr-client"))] 252 { 253 let _ = content; 254 Err(RadrootsAppError::unsupported("nostr disabled")) 255 } 256 } 257 258 pub fn nostr_fetch_text_notes( 259 &self, 260 limit: u16, 261 since_unix: Option<u64>, 262 ) -> Result<Vec<NostrPostEventMetadata>, RadrootsAppError> { 263 #[cfg(feature = "nostr-client")] 264 { 265 let mgr = nostr_manager(self)?; 266 let items = mgr 267 .fetch_post_events_blocking(limit, since_unix) 268 .map_err(|e| RadrootsAppError::relay(e.to_string()))?; 269 Ok(items.into_iter().map(map_post_event_metadata).collect()) 270 } 271 #[cfg(not(feature = "nostr-client"))] 272 { 273 let _ = (limit, since_unix); 274 Err(RadrootsAppError::unsupported("nostr disabled")) 275 } 276 } 277 278 pub fn nostr_post_reply( 279 &self, 280 parent_event_id_hex: String, 281 parent_author_hex: String, 282 content: String, 283 root_event_id_hex: Option<String>, 284 ) -> Result<String, RadrootsAppError> { 285 #[cfg(feature = "nostr-client")] 286 { 287 let mgr = nostr_manager(self)?; 288 mgr.publish_post_reply_event_blocking( 289 parent_event_id_hex, 290 parent_author_hex, 291 content, 292 root_event_id_hex, 293 ) 294 .map_err(|e| RadrootsAppError::relay(e.to_string())) 295 } 296 #[cfg(not(feature = "nostr-client"))] 297 { 298 let _ = ( 299 parent_event_id_hex, 300 parent_author_hex, 301 content, 302 root_event_id_hex, 303 ); 304 Err(RadrootsAppError::unsupported("nostr disabled")) 305 } 306 } 307 308 pub fn nostr_start_post_event_stream( 309 &self, 310 since_unix: Option<u64>, 311 ) -> Result<(), RadrootsAppError> { 312 #[cfg(feature = "nostr-client")] 313 { 314 let mgr = nostr_manager(self)?; 315 mgr.start_post_event_stream(since_unix); 316 if let Ok(mut rx_guard) = self.post_events_rx.lock() { 317 if rx_guard.is_none() { 318 *rx_guard = Some(mgr.subscribe_post_events()); 319 } 320 } 321 Ok(()) 322 } 323 #[cfg(not(feature = "nostr-client"))] 324 { 325 let _ = since_unix; 326 Err(RadrootsAppError::unsupported("nostr disabled")) 327 } 328 } 329 330 pub fn nostr_next_post_event( 331 &self, 332 ) -> Result<Option<NostrPostEventMetadata>, RadrootsAppError> { 333 #[cfg(feature = "nostr-client")] 334 { 335 let mut rx_guard = match self.post_events_rx.lock() { 336 Ok(guard) => guard, 337 Err(err) => return Err(RadrootsAppError::runtime(format!("{err}"))), 338 }; 339 let Some(rx) = rx_guard.as_mut() else { 340 return Ok(None); 341 }; 342 match rx.try_recv() { 343 Ok(event) => Ok(Some(map_post_event_metadata(event))), 344 Err(TryRecvError::Empty) => Ok(None), 345 Err(TryRecvError::Lagged(count)) => Err(RadrootsAppError::relay(format!( 346 "post event stream lagged by {count} events" 347 ))), 348 Err(TryRecvError::Closed) => { 349 *rx_guard = None; 350 Err(RadrootsAppError::relay("post event stream closed")) 351 } 352 } 353 } 354 #[cfg(not(feature = "nostr-client"))] 355 { 356 Err(RadrootsAppError::unsupported("nostr disabled")) 357 } 358 } 359 360 pub fn nostr_stop_post_event_stream(&self) -> Result<(), RadrootsAppError> { 361 #[cfg(feature = "nostr-client")] 362 { 363 let mgr = nostr_manager(self)?; 364 mgr.stop_post_event_stream(); 365 if let Ok(mut rx_guard) = self.post_events_rx.lock() { 366 *rx_guard = None; 367 } 368 Ok(()) 369 } 370 #[cfg(not(feature = "nostr-client"))] 371 { 372 Err(RadrootsAppError::unsupported("nostr disabled")) 373 } 374 } 375 }