use nostr_sdk::prelude::*; use std::{ fs::OpenOptions, io::{self, BufRead, Write}, time::Duration, }; use tauri::{Manager, State}; use crate::Nostr; async fn get_nip65_list(public_key: PublicKey, client: &Client) -> Vec { let filter = Filter::new().author(public_key).kind(Kind::RelayList).limit(1); let mut relay_list: Vec = Vec::new(); if let Ok(events) = client.get_events_of(vec![filter], Some(Duration::from_secs(10))).await { if let Some(event) = events.first() { for (url, ..) in nip65::extract_relay_list(event) { relay_list.push(url.to_string()) } } }; relay_list } #[tauri::command] #[specta::specta] pub fn get_bootstrap_relays(app: tauri::AppHandle) -> Result, String> { let relays_path = app .path() .resolve("resources/relays.txt", tauri::path::BaseDirectory::Resource) .map_err(|e| e.to_string())?; let file = std::fs::File::open(relays_path).map_err(|e| e.to_string())?; let reader = io::BufReader::new(file); reader.lines().collect::, io::Error>>().map_err(|e| e.to_string()) } #[tauri::command] #[specta::specta] pub fn set_bootstrap_relays(relays: String, app: tauri::AppHandle) -> Result<(), String> { let relays_path = app .path() .resolve("resources/relays.txt", tauri::path::BaseDirectory::Resource) .map_err(|e| e.to_string())?; let mut file = OpenOptions::new().write(true).open(relays_path).map_err(|e| e.to_string())?; file.write_all(relays.as_bytes()).map_err(|e| e.to_string()) } #[tauri::command] #[specta::specta] pub async fn get_inbox_relays( user_id: String, state: State<'_, Nostr>, ) -> Result, String> { let client = &state.client; let public_key = PublicKey::parse(user_id).map_err(|e| e.to_string())?; let inbox = Filter::new().kind(Kind::Custom(10050)).author(public_key).limit(1); match client.get_events_of(vec![inbox], None).await { Ok(events) => { if let Some(event) = events.into_iter().next() { let urls = event .tags() .iter() .filter_map(|tag| { if let Some(TagStandard::Relay(relay)) = tag.as_standardized() { Some(relay.to_string()) } else { None } }) .collect::>(); Ok(urls) } else { Ok(Vec::new()) } } Err(e) => Err(e.to_string()), } } #[tauri::command] #[specta::specta] pub async fn set_inbox_relays(relays: Vec, state: State<'_, Nostr>) -> Result<(), String> { let client = &state.client; let tags = relays.into_iter().map(|t| Tag::custom(TagKind::Relay, vec![t])).collect::>(); let event = EventBuilder::new(Kind::Custom(10050), "", tags); match client.send_event_builder(event).await { Ok(_) => Ok(()), Err(e) => Err(e.to_string()), } } #[tauri::command] #[specta::specta] pub async fn connect_inbox_relays( user_id: String, ignore_cache: bool, state: State<'_, Nostr>, ) -> Result, String> { let client = &state.client; let public_key = PublicKey::parse(&user_id).map_err(|e| e.to_string())?; let mut inbox_relays = state.inbox_relays.lock().await; if !ignore_cache { if let Some(relays) = inbox_relays.get(&public_key) { for relay in relays { let _ = client.connect_relay(relay).await; } return Ok(relays.to_owned()); }; }; let inbox = Filter::new().kind(Kind::Custom(10050)).author(public_key).limit(1); match client.get_events_of(vec![inbox], Some(Duration::from_secs(2))).await { Ok(events) => { let mut relays = Vec::new(); if let Some(event) = events.into_iter().next() { for tag in &event.tags { if let Some(TagStandard::Relay(relay)) = tag.as_standardized() { let url = relay.to_string(); let _ = client.add_relay(&url).await; let _ = client.connect_relay(&url).await; relays.push(url) } } inbox_relays.insert(public_key, relays.clone()); } // Workaround for https://github.com/rust-nostr/nostr/issues/509 // TODO: remove this // let relays_clone = relays.clone(); /*tauri::async_runtime::spawn(async move { let state = handle.state::(); let client = &state.client; client .get_events_from( relays_clone, vec![Filter::new().kind(Kind::TextNote).limit(0)], Some(Duration::from_secs(5)), ) .await }); */ Ok(relays) } Err(e) => Err(e.to_string()), } } #[tauri::command] #[specta::specta] pub async fn disconnect_inbox_relays( user_id: String, state: State<'_, Nostr>, ) -> Result<(), String> { let client = &state.client; let public_key = PublicKey::parse(&user_id).map_err(|e| e.to_string())?; let inbox_relays = state.inbox_relays.lock().await; if let Some(relays) = inbox_relays.get(&public_key) { for relay in relays { let _ = client.disconnect_relay(relay).await; } } Ok(()) }