diff --git a/Cargo.lock b/Cargo.lock index 3fd4c32..36420fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1065,7 +1065,6 @@ dependencies = [ "gpui_tokio", "indexset", "itertools 0.13.0", - "linkify", "log", "nostr", "nostr-sdk", @@ -3508,15 +3507,6 @@ dependencies = [ "rust-ini", ] -[[package]] -name = "linkify" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780" -dependencies = [ - "memchr", -] - [[package]] name = "linux-raw-sys" version = "0.4.15" diff --git a/crates/auto_update/src/lib.rs b/crates/auto_update/src/lib.rs index c25123a..78a3dc5 100644 --- a/crates/auto_update/src/lib.rs +++ b/crates/auto_update/src/lib.rs @@ -197,7 +197,11 @@ impl AutoUpdater { }); } Err(e) => { - log::warn!("{e}") + _ = this.update(cx, |this, cx| { + this.set_status(AutoUpdateStatus::Idle, cx); + }); + + log::warn!("{e}"); } } }), diff --git a/crates/chat/src/room.rs b/crates/chat/src/room.rs index b55f0c7..513f6f2 100644 --- a/crates/chat/src/room.rs +++ b/crates/chat/src/room.rs @@ -371,6 +371,7 @@ impl Room { continue; }; + // Construct a filter for messaging relays let filter = Filter::new() .kind(Kind::InboxRelays) .author(member) @@ -379,6 +380,7 @@ impl Room { // Subscribe to get members messaging relays client.subscribe(filter, Some(opts)).await?; + // Construct a filter for encryption keys announcement let filter = Filter::new() .kind(Kind::Custom(10044)) .author(member) @@ -392,42 +394,6 @@ impl Room { }) } - pub fn verify_connections(&self, cx: &App) -> Task, Error>> { - let members = self.members(); - - cx.background_spawn(async move { - let client = app_state().client(); - let mut result = HashMap::default(); - - for member in members.into_iter() { - let filter = Filter::new() - .kind(Kind::InboxRelays) - .author(member) - .limit(1); - - if let Some(event) = client.database().query(filter).await?.first() { - let urls: Vec<&RelayUrl> = nip17::extract_relay_list(event).collect(); - - if urls.is_empty() { - result.insert(member, false); - continue; - } - - for url in urls { - client.add_relay(url).await.ok(); - client.connect_relay(url).await.ok(); - } - - result.insert(member, true); - } else { - result.insert(member, false); - } - } - - Ok(result) - }) - } - /// Get all messages belonging to the room pub fn get_messages(&self, cx: &App) -> Task, Error>> { let conversation_id = self.id.to_string(); diff --git a/crates/chat_ui/Cargo.toml b/crates/chat_ui/Cargo.toml index b381a2a..e82ca7b 100644 --- a/crates/chat_ui/Cargo.toml +++ b/crates/chat_ui/Cargo.toml @@ -30,5 +30,4 @@ serde_json.workspace = true indexset = "0.12.3" emojis = "0.6.4" once_cell = "1.19.0" -linkify = "0.10.0" regex = "1" diff --git a/crates/chat_ui/src/lib.rs b/crates/chat_ui/src/lib.rs index fe64b8b..2e1cabf 100644 --- a/crates/chat_ui/src/lib.rs +++ b/crates/chat_ui/src/lib.rs @@ -22,7 +22,7 @@ use person::PersonRegistry; use settings::AppSettings; use smallvec::{smallvec, SmallVec}; use smol::fs; -use states::{app_state, SignerKind, QUERY_TIMEOUT}; +use states::{app_state, SignerKind}; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; @@ -45,9 +45,6 @@ mod emoji; mod subject; mod text; -const NIP17_WARN: &str = "has not set up Messaging Relays, they cannot receive your message."; -const EMPTY_WARN: &str = "Something is wrong. Coop cannot display this message"; - pub fn init(room: Entity, window: &mut Window, cx: &mut App) -> Entity { cx.new(|cx| ChatPanel::new(room, window, cx)) } @@ -77,7 +74,7 @@ pub struct ChatPanel { image_cache: Entity, _subscriptions: SmallVec<[Subscription; 3]>, - _tasks: SmallVec<[Task<()>; 3]>, + _tasks: SmallVec<[Task<()>; 2]>, } impl ChatPanel { @@ -99,21 +96,11 @@ impl ChatPanel { let list_state = ListState::new(messages.len(), ListAlignment::Bottom, px(1024.)); let connect = room.read(cx).connect(cx); - let verify_connections = room.read(cx).verify_connections(cx); let get_messages = room.read(cx).get_messages(cx); let mut subscriptions = smallvec![]; let mut tasks = smallvec![]; - tasks.push( - // Get messaging relays and encryption keys announcement for all members - cx.background_spawn(async move { - if let Err(e) = connect.await { - log::error!("Failed to initialize room: {e}"); - } - }), - ); - tasks.push( // Load all messages belonging to this room cx.spawn_in(window, async move |this, cx| { @@ -134,35 +121,11 @@ impl ChatPanel { ); tasks.push( - // Connect and verify all members messaging relays - cx.spawn_in(window, async move |this, cx| { - // Wait for 5 seconds before connecting and verifying - cx.background_executor() - .timer(Duration::from_secs(QUERY_TIMEOUT)) - .await; - - let result = verify_connections.await; - - this.update_in(cx, |this, window, cx| { - match result { - Ok(data) => { - let persons = PersonRegistry::global(cx); - - for (public_key, status) in data.into_iter() { - if !status { - let profile = persons.read(cx).get_person(&public_key, cx); - let name = profile.display_name(); - - this.insert_warning(format!("{NIP17_WARN} {name}"), cx); - } - } - } - Err(e) => { - window.push_notification(e.to_string(), cx); - } - }; - }) - .ok(); + // Get messaging relays and encryption keys announcement for each member + cx.background_spawn(async move { + if let Err(e) = connect.await { + log::error!("Failed to initialize room: {}", e); + } }), ); @@ -663,6 +626,33 @@ impl ChatPanel { } fn render_message( + &mut self, + ix: usize, + window: &mut Window, + cx: &mut Context, + ) -> AnyElement { + if let Some(message) = self.messages.get_index(ix) { + match message { + Message::User(rendered) => { + let text = self + .rendered_texts_by_id + .entry(rendered.id) + .or_insert_with(|| RenderedText::new(&rendered.content, cx)) + .element(ix.into(), window, cx); + + self.render_text_message(ix, rendered, text, cx) + } + Message::Warning(content, _timestamp) => { + self.render_warning(ix, SharedString::from(content), cx) + } + Message::System(_timestamp) => self.render_announcement(ix, cx), + } + } else { + self.render_warning(ix, SharedString::from("Message not found"), cx) + } + } + + fn render_text_message( &self, ix: usize, message: &RenderedMessage, @@ -1358,26 +1348,9 @@ impl Render for ChatPanel { .child( list( self.list_state.clone(), - cx.processor(move |this, ix: usize, window, cx| { - if let Some(message) = this.messages.get_index(ix) { - match message { - Message::User(rendered) => { - let text = this - .rendered_texts_by_id - .entry(rendered.id) - .or_insert_with(|| RenderedText::new(&rendered.content, cx)) - .element(ix.into(), window, cx); - - this.render_message(ix, rendered, text, cx) - } - Message::Warning(content, _timestamp) => { - this.render_warning(ix, SharedString::from(content), cx) - } - Message::System(_timestamp) => this.render_announcement(ix, cx), - } - } else { - this.render_warning(ix, SharedString::from(EMPTY_WARN), cx) - } + cx.processor(|this, ix, window, cx| { + // Get and render message by index + this.render_message(ix, window, cx) }), ) .flex_1(), diff --git a/crates/chat_ui/src/text.rs b/crates/chat_ui/src/text.rs index 73c4fca..970af60 100644 --- a/crates/chat_ui/src/text.rs +++ b/crates/chat_ui/src/text.rs @@ -3,10 +3,9 @@ use std::sync::Arc; use common::display::RenderedProfile; use gpui::{ - AnyElement, AnyView, App, ElementId, HighlightStyle, InteractiveText, IntoElement, - SharedString, StyledText, UnderlineStyle, Window, + AnyElement, App, ElementId, HighlightStyle, InteractiveText, IntoElement, SharedString, + StyledText, UnderlineStyle, Window, }; -use linkify::{LinkFinder, LinkKind}; use nostr_sdk::prelude::*; use once_cell::sync::Lazy; use person::PersonRegistry; @@ -16,7 +15,7 @@ use theme::ActiveTheme; use crate::actions::OpenPublicKey; static URL_REGEX: Lazy = Lazy::new(|| { - Regex::new(r"^(?:[a-zA-Z]+://)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(:\d+)?(/.*)?$").unwrap() + Regex::new(r"(?i)(?:^|\s)(?:https?://)?(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}(?::\d+)?(?:/[^\s]*)?(?:\s|$)").unwrap() }); static NOSTR_URI_REGEX: Lazy = @@ -24,43 +23,16 @@ static NOSTR_URI_REGEX: Lazy = #[derive(Debug, Clone, PartialEq, Eq)] pub enum Highlight { - Link(HighlightStyle), + Link, Nostr, } -impl Highlight { - fn link() -> Self { - Self::Link(HighlightStyle { - underline: Some(UnderlineStyle { - thickness: 1.0.into(), - ..Default::default() - }), - ..Default::default() - }) - } - - fn nostr() -> Self { - Self::Nostr - } -} - -impl From for Highlight { - fn from(style: HighlightStyle) -> Self { - Self::Link(style) - } -} - -type CustomRangeTooltipFn = - Option, &mut Window, &mut App) -> Option>>; - #[derive(Default)] pub struct RenderedText { pub text: SharedString, pub highlights: Vec<(Range, Highlight)>, pub link_ranges: Vec>, pub link_urls: Arc<[String]>, - pub custom_ranges: Vec>, - custom_ranges_tooltip_fn: CustomRangeTooltipFn, } impl RenderedText { @@ -86,19 +58,9 @@ impl RenderedText { link_urls: link_urls.into(), link_ranges, highlights, - custom_ranges: Vec::new(), - custom_ranges_tooltip_fn: None, } } - #[allow(dead_code)] - pub fn set_tooltip_builder_for_custom_ranges(&mut self, f: F) - where - F: Fn(usize, Range, &mut Window, &mut App) -> Option + 'static, - { - self.custom_ranges_tooltip_fn = Some(Arc::new(f)); - } - pub fn element(&self, id: ElementId, window: &Window, cx: &App) -> AnyElement { let link_color = cx.theme().text_accent; @@ -110,17 +72,11 @@ impl RenderedText { ( range.clone(), match highlight { - Highlight::Link(highlight) => { - // Check if this is a link highlight by seeing if it has an underline - if highlight.underline.is_some() { - // It's a link, so apply the link color - let mut link_style = *highlight; - link_style.color = Some(link_color); - link_style - } else { - *highlight - } - } + Highlight::Link => HighlightStyle { + color: Some(link_color), + underline: Some(UnderlineStyle::default()), + ..Default::default() + }, Highlight::Nostr => HighlightStyle { color: Some(link_color), ..Default::default() @@ -135,49 +91,22 @@ impl RenderedText { move |ix, window, cx| { let token = link_urls[ix].as_str(); - if token.starts_with("nostr:") { - let clean_url = token.replace("nostr:", ""); - let Ok(public_key) = PublicKey::parse(&clean_url) else { - log::error!("Failed to parse public key from: {clean_url}"); - return; - }; - window.dispatch_action(Box::new(OpenPublicKey(public_key)), cx); - } else if is_url(token) { - if !token.starts_with("http") { - cx.open_url(&format!("https://{token}")); - } else { - cx.open_url(token); + if let Some(clean_url) = token.strip_prefix("nostr:") { + if let Ok(public_key) = PublicKey::parse(clean_url) { + window.dispatch_action(Box::new(OpenPublicKey(public_key)), cx); } + } else if is_url(token) { + let url = if token.starts_with("http") { + token.to_string() + } else { + format!("https://{token}") + }; + cx.open_url(&url); } else { log::warn!("Unrecognized token {token}") } } }) - .tooltip({ - let link_ranges = self.link_ranges.clone(); - let link_urls = self.link_urls.clone(); - let custom_tooltip_ranges = self.custom_ranges.clone(); - let custom_tooltip_fn = self.custom_ranges_tooltip_fn.clone(); - move |idx, window, cx| { - for (ix, range) in link_ranges.iter().enumerate() { - if range.contains(&idx) { - let url = &link_urls[ix]; - if url.starts_with("http") { - // return Some(LinkPreview::new(url, cx)); - } - // You can add custom tooltip handling for mentions here - } - } - for range in &custom_tooltip_ranges { - if range.contains(&idx) { - if let Some(f) = &custom_tooltip_fn { - return f(idx, range.clone(), window, cx); - } - } - } - None - } - }) .into_any_element() } } @@ -193,20 +122,15 @@ fn render_plain_text_mut( // Copy the content directly text.push_str(content); - // Initialize the link finder - let mut finder = LinkFinder::new(); - finder.url_must_have_scheme(false); - finder.kinds(&[LinkKind::Url]); - // Collect all URLs let mut url_matches: Vec<(Range, String)> = Vec::new(); - for link in finder.links(content) { - let start = link.start(); - let end = link.end(); - let range = start..end; + for link in URL_REGEX.find_iter(content) { + let range = link.start()..link.end(); let url = link.as_str().to_string(); + log::info!("Found URL: {}", url); + url_matches.push((range, url)); } @@ -214,9 +138,7 @@ fn render_plain_text_mut( let mut nostr_matches: Vec<(Range, String)> = Vec::new(); for nostr_match in NOSTR_URI_REGEX.find_iter(content) { - let start = nostr_match.start(); - let end = nostr_match.end(); - let range = start..end; + let range = nostr_match.start()..nostr_match.end(); let nostr_uri = nostr_match.as_str().to_string(); // Check if this nostr URI overlaps with any already processed URL @@ -240,12 +162,9 @@ fn render_plain_text_mut( for (range, entity) in all_matches { // Handle URL token if is_url(&entity) { - // Add underline highlight - highlights.push((range.clone(), Highlight::link())); - // Make it clickable + highlights.push((range.clone(), Highlight::Link)); link_ranges.push(range); link_urls.push(entity); - continue; }; @@ -306,75 +225,6 @@ fn render_plain_text_mut( } } } - - fn render_pubkey( - public_key: PublicKey, - text: &mut String, - range: &Range, - highlights: &mut Vec<(Range, Highlight)>, - link_ranges: &mut Vec>, - link_urls: &mut Vec, - cx: &App, - ) { - let persons = PersonRegistry::global(cx); - let profile = persons.read(cx).get_person(&public_key, cx); - let display_name = format!("@{}", profile.display_name()); - - // Replace token with display name - text.replace_range(range.clone(), &display_name); - - // Adjust ranges - let new_length = display_name.len(); - let length_diff = new_length as isize - (range.end - range.start) as isize; - // New range for the replacement - let new_range = range.start..(range.start + new_length); - - // Add highlight for the profile name - highlights.push((new_range.clone(), Highlight::nostr())); - // Make it clickable - link_ranges.push(new_range); - link_urls.push(format!("nostr:{}", profile.public_key().to_hex())); - - // Adjust subsequent ranges if needed - if length_diff != 0 { - adjust_ranges(highlights, link_ranges, range.end, length_diff); - } - } - - fn render_bech32( - bech32: String, - text: &mut String, - range: &Range, - highlights: &mut Vec<(Range, Highlight)>, - link_ranges: &mut Vec>, - link_urls: &mut Vec, - ) { - let njump_url = format!("https://njump.me/{bech32}"); - - // Create a shortened display format for the URL - let shortened_entity = format_shortened_entity(&bech32); - let display_text = format!("https://njump.me/{shortened_entity}"); - - // Replace the original entity with the shortened display version - text.replace_range(range.clone(), &display_text); - - // Adjust the ranges - let new_length = display_text.len(); - let length_diff = new_length as isize - (range.end - range.start) as isize; - // New range for the replacement - let new_range = range.start..(range.start + new_length); - - // Add underline highlight - highlights.push((new_range.clone(), Highlight::link())); - // Make it clickable - link_ranges.push(new_range); - link_urls.push(njump_url); - - // Adjust subsequent ranges if needed - if length_diff != 0 { - adjust_ranges(highlights, link_ranges, range.end, length_diff); - } - } } /// Check if a string is a URL @@ -396,6 +246,61 @@ fn format_shortened_entity(entity: &str) -> String { } } +fn render_pubkey( + public_key: PublicKey, + text: &mut String, + range: &Range, + highlights: &mut Vec<(Range, Highlight)>, + link_ranges: &mut Vec>, + link_urls: &mut Vec, + cx: &App, +) { + let persons = PersonRegistry::global(cx); + let profile = persons.read(cx).get_person(&public_key, cx); + let display_name = format!("@{}", profile.display_name()); + + text.replace_range(range.clone(), &display_name); + + let new_length = display_name.len(); + let length_diff = new_length as isize - (range.end - range.start) as isize; + let new_range = range.start..(range.start + new_length); + + highlights.push((new_range.clone(), Highlight::Nostr)); + link_ranges.push(new_range); + link_urls.push(format!("nostr:{}", profile.public_key().to_hex())); + + if length_diff != 0 { + adjust_ranges(highlights, link_ranges, range.end, length_diff); + } +} + +fn render_bech32( + bech32: String, + text: &mut String, + range: &Range, + highlights: &mut Vec<(Range, Highlight)>, + link_ranges: &mut Vec>, + link_urls: &mut Vec, +) { + let njump_url = format!("https://njump.me/{bech32}"); + let shortened_entity = format_shortened_entity(&bech32); + let display_text = format!("https://njump.me/{shortened_entity}"); + + text.replace_range(range.clone(), &display_text); + + let new_length = display_text.len(); + let length_diff = new_length as isize - (range.end - range.start) as isize; + let new_range = range.start..(range.start + new_length); + + highlights.push((new_range.clone(), Highlight::Link)); + link_ranges.push(new_range); + link_urls.push(njump_url); + + if length_diff != 0 { + adjust_ranges(highlights, link_ranges, range.end, length_diff); + } +} + // Helper function to adjust ranges when text length changes fn adjust_ranges( highlights: &mut [(Range, Highlight)], diff --git a/crates/common/src/display.rs b/crates/common/src/display.rs index c555209..dfa4ad2 100644 --- a/crates/common/src/display.rs +++ b/crates/common/src/display.rs @@ -6,7 +6,6 @@ use gpui::{Image, ImageFormat, SharedString, SharedUri}; use nostr_sdk::prelude::*; use qrcode::render::svg; use qrcode::QrCode; -use states::IMAGE_RESIZE_SERVICE; const NOW: &str = "now"; const SECONDS_IN_MINUTE: i64 = 60; @@ -14,6 +13,7 @@ const MINUTES_IN_HOUR: i64 = 60; const HOURS_IN_DAY: i64 = 24; const DAYS_IN_MONTH: i64 = 30; const FALLBACK_IMG: &str = "https://image.nostr.build/c30703b48f511c293a9003be8100cdad37b8798b77a1dc3ec6eb8a20443d5dea.png"; +const IMAGE_RESIZE_SERVICE: &str = "https://wsrv.nl"; pub trait RenderedProfile { fn avatar(&self, proxy: bool) -> SharedUri; diff --git a/crates/coop/src/chatspace.rs b/crates/coop/src/chatspace.rs index 5d1cc42..ef1c602 100644 --- a/crates/coop/src/chatspace.rs +++ b/crates/coop/src/chatspace.rs @@ -1167,42 +1167,41 @@ impl ChatSpace { let file_keystore = KeyStore::global(cx).read(cx).is_using_file_keystore(); let proxy = AppSettings::get_proxy_user_avatars(cx); let auth_requests = self.auth_requests.read(cx).len(); + let auto_update = AutoUpdater::global(cx); h_flex() .gap_1() - .map( - |this| match AutoUpdater::global(cx).read(cx).status.as_ref() { - AutoUpdateStatus::Checking => this.child( - div() - .text_xs() - .text_color(cx.theme().text_muted) - .child(SharedString::from("Checking for Coop updates...")), - ), - AutoUpdateStatus::Installing => this.child( - div() - .text_xs() - .text_color(cx.theme().text_muted) - .child(SharedString::from("Installing updates...")), - ), - AutoUpdateStatus::Errored { msg } => this.child( - div() - .text_xs() - .text_color(cx.theme().text_muted) - .child(SharedString::from(msg.as_ref())), - ), - AutoUpdateStatus::Updated => this.child( - div() - .id("restart") - .text_xs() - .text_color(cx.theme().text_muted) - .child(SharedString::from("Updated. Click to restart")) - .on_click(|_ev, _window, cx| { - cx.restart(); - }), - ), - _ => this.child(div()), - }, - ) + .map(|this| match auto_update.read(cx).status.as_ref() { + AutoUpdateStatus::Checking => this.child( + div() + .text_xs() + .text_color(cx.theme().text_muted) + .child(SharedString::from("Checking for Coop updates...")), + ), + AutoUpdateStatus::Installing => this.child( + div() + .text_xs() + .text_color(cx.theme().text_muted) + .child(SharedString::from("Installing updates...")), + ), + AutoUpdateStatus::Errored { msg } => this.child( + div() + .text_xs() + .text_color(cx.theme().text_muted) + .child(SharedString::from(msg.as_ref())), + ), + AutoUpdateStatus::Updated => this.child( + div() + .id("restart") + .text_xs() + .text_color(cx.theme().text_muted) + .child(SharedString::from("Updated. Click to restart")) + .on_click(|_ev, _window, cx| { + cx.restart(); + }), + ), + _ => this.child(div()), + }) .when(file_keystore, |this| { this.child( Button::new("keystore-warning") diff --git a/crates/coop/src/main.rs b/crates/coop/src/main.rs index 9c467d3..ccda44a 100644 --- a/crates/coop/src/main.rs +++ b/crates/coop/src/main.rs @@ -6,7 +6,7 @@ use gpui::{ TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowOptions, }; -use states::{app_state, APP_ID, CLIENT_NAME}; +use states::{app_state, APP_ID, BOOTSTRAP_RELAYS, CLIENT_NAME, SEARCH_RELAYS}; use ui::Root; use crate::actions::{load_embedded_fonts, quit, Quit}; @@ -21,14 +21,34 @@ fn main() { // Initialize logging tracing_subscriber::fmt::init(); - // Initialize the coop simple storage - let _app_state = app_state(); - // Initialize the Application let app = Application::new() .with_assets(Assets) .with_http_client(Arc::new(reqwest_client::ReqwestClient::new())); + // Initialize app state + let app_state = app_state(); + + // Connect to relays + app.background_executor() + .spawn(async move { + let client = app_state.client(); + + // Get all bootstrapping relays + let mut urls = vec![]; + urls.extend(BOOTSTRAP_RELAYS); + urls.extend(SEARCH_RELAYS); + + // Add relay to the relay pool + for url in urls.into_iter() { + client.add_relay(url).await.ok(); + } + + // Establish connection to relays + client.connect().await; + }) + .detach(); + // Run application app.run(move |cx| { // Load embedded fonts in assets/fonts diff --git a/crates/coop/src/views/sidebar/mod.rs b/crates/coop/src/views/sidebar/mod.rs index e5b9e51..cbf98d0 100644 --- a/crates/coop/src/views/sidebar/mod.rs +++ b/crates/coop/src/views/sidebar/mod.rs @@ -6,7 +6,7 @@ use anyhow::{anyhow, Error}; use chat::room::{Room, RoomKind}; use chat::{ChatEvent, ChatRegistry}; use common::debounced_delay::DebouncedDelay; -use common::display::{RenderedProfile, RenderedTimestamp, TextUtils}; +use common::display::{RenderedTimestamp, TextUtils}; use gpui::prelude::FluentBuilder; use gpui::{ deferred, div, relative, uniform_list, AnyElement, App, AppContext, Context, Entity, @@ -628,8 +628,8 @@ impl Sidebar { items.push( RoomListItem::new(ix) .room_id(room_id) - .name(member.display_name()) - .avatar(member.avatar(proxy)) + .name(this.display_name(cx)) + .avatar(this.display_image(proxy, cx)) .public_key(member.public_key()) .kind(this.kind) .created_at(this.created_at.to_ago()) diff --git a/crates/states/src/constants.rs b/crates/states/src/constants.rs index 9eb6bc7..a57ecd2 100644 --- a/crates/states/src/constants.rs +++ b/crates/states/src/constants.rs @@ -42,9 +42,3 @@ pub const METADATA_BATCH_TIMEOUT: u64 = 300; /// Default width of the sidebar. pub const DEFAULT_SIDEBAR_WIDTH: f32 = 240.; - -/// Image Resize Service -pub const IMAGE_RESIZE_SERVICE: &str = "https://wsrv.nl"; - -/// Default NIP96 Media Server. -pub const NIP96_SERVER: &str = "https://nostrmedia.com"; diff --git a/crates/states/src/state/mod.rs b/crates/states/src/state/mod.rs index 496ebad..b4e7fe3 100644 --- a/crates/states/src/state/mod.rs +++ b/crates/states/src/state/mod.rs @@ -12,7 +12,7 @@ use nostr_sdk::prelude::*; use smol::lock::RwLock; use crate::constants::{ - BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT, QUERY_TIMEOUT, SEARCH_RELAYS, + BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT, QUERY_TIMEOUT, }; use crate::paths::config_dir; use crate::state::ingester::Ingester; @@ -210,19 +210,6 @@ impl AppState { /// Handles events from the nostr client pub async fn handle_notifications(&self) -> Result<(), Error> { - // Get all bootstrapping relays - let mut urls = vec![]; - urls.extend(BOOTSTRAP_RELAYS); - urls.extend(SEARCH_RELAYS); - - // Add relay to the relay pool - for url in urls.into_iter() { - self.client.add_relay(url).await?; - } - - // Establish connection to relays - self.client.connect().await; - let mut processed_events: HashSet = HashSet::new(); let mut challenges: HashSet> = HashSet::new(); let mut notifications = self.client.notifications(); @@ -345,9 +332,7 @@ impl AppState { self.signal.send(SignalKind::NewProfile(profile)).await; } Kind::GiftWrap => { - if let Err(e) = self.extract_rumor(&event).await { - log::error!("Failed to extract rumor: {e}"); - } + self.extract_rumor(&event).await.ok(); } _ => {} } @@ -997,6 +982,8 @@ impl AppState { .subscribe_with_id_to(&urls, id, filter, None) .await?; + log::info!("Subscribed to gift wrap events"); + Ok(()) } @@ -1027,15 +1014,15 @@ impl AppState { } /// Stores an unwrapped event in local database with reference to original - async fn set_rumor(&self, id: EventId, rumor: &UnsignedEvent) -> Result<(), Error> { + async fn set_rumor(&self, gift_wrap: EventId, rumor: &UnsignedEvent) -> Result<(), Error> { let rumor_id = rumor.id.context("Rumor is missing an event id")?; let author = rumor.pubkey; - let conversation = self.conversation_id(rumor).to_string(); + let conversation = self.conversation_id(rumor); let mut tags = rumor.tags.clone().to_vec(); // Add a unique identifier - tags.push(Tag::identifier(id)); + tags.push(Tag::identifier(gift_wrap)); // Add a reference to the rumor's author tags.push(Tag::custom( @@ -1046,7 +1033,7 @@ impl AppState { // Add a conversation id tags.push(Tag::custom( TagKind::SingleLetter(SingleLetterTag::lowercase(Alphabet::C)), - [conversation], + [conversation.to_string()], )); // Add a reference to the rumor's id @@ -1063,21 +1050,23 @@ impl AppState { // Convert rumor to json let content = rumor.as_json(); + // Construct the event let event = EventBuilder::new(Kind::ApplicationSpecificData, content) .tags(tags) .sign(&Keys::generate()) .await?; + // Save the event to the database self.client.database().save_event(&event).await?; Ok(()) } /// Retrieves a previously unwrapped event from local database - async fn get_rumor(&self, id: EventId) -> Result { + async fn get_rumor(&self, gift_wrap: EventId) -> Result { let filter = Filter::new() .kind(Kind::ApplicationSpecificData) - .identifier(id) + .identifier(gift_wrap) .limit(1); if let Some(event) = self.client.database().query(filter).await?.first_owned() { @@ -1118,20 +1107,15 @@ impl AppState { // Helper method to try unwrapping with different signers async fn try_unwrap_gift_wrap(&self, gift_wrap: &Event) -> Result { - // Try to unwrap with the encryption key first + // Try to unwrap with the encryption key if available // NIP-4e: https://github.com/nostr-protocol/nips/blob/per-device-keys/4e.md if let Some(signer) = self.device.read().await.encryption.as_ref() { - match UnwrappedGift::from_gift_wrap(signer, gift_wrap).await { - Ok(unwrapped) => { - return Ok(unwrapped); - } - Err(e) => { - log::warn!("Failed to unwrap with the encryption key: {e}") - } + if let Ok(unwrapped) = UnwrappedGift::from_gift_wrap(signer, gift_wrap).await { + return Ok(unwrapped); } } - // Try to unwrap with the user's signer + // Fallback to unwrap with the user's signer let signer = self.client.signer().await?; let unwrapped = UnwrappedGift::from_gift_wrap(&signer, gift_wrap).await?;