Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b20131e3b | |||
|
|
9127696517 | ||
|
|
af74a4ed23 | ||
|
|
bd2b72a57a |
26
Cargo.lock
generated
26
Cargo.lock
generated
@@ -178,7 +178,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "assets"
|
name = "assets"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -417,7 +417,7 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "auto_update"
|
name = "auto_update"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"cargo-packager-updater",
|
"cargo-packager-updater",
|
||||||
@@ -1021,7 +1021,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "client_keys"
|
name = "client_keys"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"global",
|
"global",
|
||||||
@@ -1158,7 +1158,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "common"
|
name = "common"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -1214,7 +1214,7 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "coop"
|
name = "coop"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"assets",
|
"assets",
|
||||||
@@ -2342,7 +2342,7 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "global"
|
name = "global"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"dirs 5.0.1",
|
"dirs 5.0.1",
|
||||||
@@ -2885,7 +2885,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "i18n"
|
name = "i18n"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rust-i18n",
|
"rust-i18n",
|
||||||
]
|
]
|
||||||
@@ -3002,7 +3002,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "identity"
|
name = "identity"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"client_keys",
|
"client_keys",
|
||||||
@@ -5020,7 +5020,7 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "registry"
|
name = "registry"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -5811,7 +5811,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "settings"
|
name = "settings"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"global",
|
"global",
|
||||||
@@ -6374,7 +6374,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "theme"
|
name = "theme"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"gpui",
|
"gpui",
|
||||||
@@ -6534,7 +6534,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "title_bar"
|
name = "title_bar"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"common",
|
"common",
|
||||||
@@ -6905,7 +6905,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ui"
|
name = "ui"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"common",
|
"common",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ members = ["crates/*"]
|
|||||||
default-members = ["crates/coop"]
|
default-members = ["crates/coop"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
publish = false
|
publish = false
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ product-name = "Coop"
|
|||||||
description = "Chat Freely, Stay Private on Nostr"
|
description = "Chat Freely, Stay Private on Nostr"
|
||||||
identifier = "su.reya.coop"
|
identifier = "su.reya.coop"
|
||||||
category = "SocialNetworking"
|
category = "SocialNetworking"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
out-dir = "../../dist"
|
out-dir = "../../dist"
|
||||||
before-packaging-command = "cargo build --release"
|
before-packaging-command = "cargo build --release"
|
||||||
resources = ["Cargo.toml", "src"]
|
resources = ["Cargo.toml", "src"]
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ use anyhow::{anyhow, Error};
|
|||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
use common::event::EventUtils;
|
use common::event::EventUtils;
|
||||||
use global::constants::{
|
use global::constants::{
|
||||||
ALL_MESSAGES_ID, APP_ID, APP_NAME, BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT,
|
APP_ID, APP_NAME, BOOTSTRAP_RELAYS, METADATA_BATCH_LIMIT, METADATA_BATCH_TIMEOUT,
|
||||||
METADATA_BATCH_TIMEOUT, NEW_MESSAGE_ID, SEARCH_RELAYS,
|
SEARCH_RELAYS, WAIT_FOR_FINISH,
|
||||||
};
|
};
|
||||||
use global::{nostr_client, set_all_gift_wraps_fetched, NostrSignal};
|
use global::{gift_wrap_sub_id, nostr_client, starting_time, NostrSignal};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, point, px, size, App, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem,
|
actions, point, px, size, App, AppContext, Application, Bounds, KeyBinding, Menu, MenuItem,
|
||||||
SharedString, TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
|
SharedString, TitlebarOptions, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
|
||||||
@@ -39,6 +39,9 @@ fn main() {
|
|||||||
// Initialize the Nostr Client
|
// Initialize the Nostr Client
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
|
|
||||||
|
// Initialize the starting time
|
||||||
|
let _ = starting_time();
|
||||||
|
|
||||||
// Initialize the Application
|
// Initialize the Application
|
||||||
let app = Application::new()
|
let app = Application::new()
|
||||||
.with_assets(Assets)
|
.with_assets(Assets)
|
||||||
@@ -46,7 +49,7 @@ fn main() {
|
|||||||
|
|
||||||
let (signal_tx, signal_rx) = channel::bounded::<NostrSignal>(2048);
|
let (signal_tx, signal_rx) = channel::bounded::<NostrSignal>(2048);
|
||||||
let (mta_tx, mta_rx) = channel::bounded::<PublicKey>(1024);
|
let (mta_tx, mta_rx) = channel::bounded::<PublicKey>(1024);
|
||||||
let (event_tx, event_rx) = channel::unbounded::<Event>();
|
let (event_tx, event_rx) = channel::bounded::<Event>(2048);
|
||||||
|
|
||||||
let signal_tx_clone = signal_tx.clone();
|
let signal_tx_clone = signal_tx.clone();
|
||||||
let mta_tx_clone = mta_tx.clone();
|
let mta_tx_clone = mta_tx.clone();
|
||||||
@@ -131,7 +134,7 @@ fn main() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let duration = smol::Timer::after(Duration::from_secs(30));
|
let duration = smol::Timer::after(Duration::from_secs(WAIT_FOR_FINISH));
|
||||||
|
|
||||||
let recv = || async {
|
let recv = || async {
|
||||||
// no inline
|
// no inline
|
||||||
@@ -145,8 +148,7 @@ fn main() {
|
|||||||
|
|
||||||
match smol::future::or(recv(), timeout()).await {
|
match smol::future::or(recv(), timeout()).await {
|
||||||
Some(event) => {
|
Some(event) => {
|
||||||
// Process the gift wrap event unwrapping
|
let cached = try_unwrap_event(&event, &signal_tx, &mta_tx).await;
|
||||||
let cached = try_unwrap_event(&signal_tx, &mta_tx, &event, false).await;
|
|
||||||
|
|
||||||
// Increment the total messages counter if message is not from cache
|
// Increment the total messages counter if message is not from cache
|
||||||
if !cached {
|
if !cached {
|
||||||
@@ -163,17 +165,9 @@ fn main() {
|
|||||||
None => {
|
None => {
|
||||||
// Notify the UI that the processing is finished
|
// Notify the UI that the processing is finished
|
||||||
signal_tx.send(NostrSignal::Finish).await.ok();
|
signal_tx.send(NostrSignal::Finish).await.ok();
|
||||||
// Mark all gift wraps as fetched
|
|
||||||
// For the next time Coop only needs to process new gift wraps
|
|
||||||
set_all_gift_wraps_fetched().await;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event channel is no longer needed when all gift wrap events have been processed
|
|
||||||
event_rx.close();
|
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
@@ -246,8 +240,6 @@ fn main() {
|
|||||||
|
|
||||||
// Spawn a task to handle events from nostr channel
|
// Spawn a task to handle events from nostr channel
|
||||||
cx.spawn_in(window, async move |_, cx| {
|
cx.spawn_in(window, async move |_, cx| {
|
||||||
let all_messages = SubscriptionId::new(ALL_MESSAGES_ID);
|
|
||||||
|
|
||||||
while let Ok(signal) = signal_rx.recv().await {
|
while let Ok(signal) = signal_rx.recv().await {
|
||||||
cx.update(|window, cx| {
|
cx.update(|window, cx| {
|
||||||
let registry = Registry::global(cx);
|
let registry = Registry::global(cx);
|
||||||
@@ -277,8 +269,8 @@ fn main() {
|
|||||||
}
|
}
|
||||||
// Load chat rooms without setting as finished
|
// Load chat rooms without setting as finished
|
||||||
NostrSignal::Eose(subscription_id) => {
|
NostrSignal::Eose(subscription_id) => {
|
||||||
// Only load chat rooms if the subscription ID matches the all_messages_sub_id
|
// Only load chat rooms if the subscription matches the gift wrap subscription
|
||||||
if subscription_id == all_messages {
|
if gift_wrap_sub_id() == &subscription_id {
|
||||||
registry.update(cx, |this, cx| {
|
registry.update(cx, |this, cx| {
|
||||||
this.load_rooms(window, cx);
|
this.load_rooms(window, cx);
|
||||||
});
|
});
|
||||||
@@ -369,9 +361,7 @@ async fn handle_nostr_notifications(
|
|||||||
mta_tx: &Sender<PublicKey>,
|
mta_tx: &Sender<PublicKey>,
|
||||||
event_tx: &Sender<Event>,
|
event_tx: &Sender<Event>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let new_messages = SubscriptionId::new(NEW_MESSAGE_ID);
|
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
|
|
||||||
let mut notifications = client.notifications();
|
let mut notifications = client.notifications();
|
||||||
let mut processed_events: BTreeSet<EventId> = BTreeSet::new();
|
let mut processed_events: BTreeSet<EventId> = BTreeSet::new();
|
||||||
let mut processed_dm_relays: BTreeSet<PublicKey> = BTreeSet::new();
|
let mut processed_dm_relays: BTreeSet<PublicKey> = BTreeSet::new();
|
||||||
@@ -382,10 +372,7 @@ async fn handle_nostr_notifications(
|
|||||||
};
|
};
|
||||||
|
|
||||||
match message {
|
match message {
|
||||||
RelayMessage::Event {
|
RelayMessage::Event { event, .. } => {
|
||||||
event,
|
|
||||||
subscription_id,
|
|
||||||
} => {
|
|
||||||
if processed_events.contains(&event.id) {
|
if processed_events.contains(&event.id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -394,12 +381,7 @@ async fn handle_nostr_notifications(
|
|||||||
|
|
||||||
match event.kind {
|
match event.kind {
|
||||||
Kind::GiftWrap => {
|
Kind::GiftWrap => {
|
||||||
if *subscription_id == new_messages {
|
event_tx.send(event.into_owned()).await.ok();
|
||||||
let event = event.as_ref();
|
|
||||||
_ = try_unwrap_event(signal_tx, mta_tx, event, false).await;
|
|
||||||
} else {
|
|
||||||
event_tx.send(event.into_owned()).await.ok();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Kind::Metadata => {
|
Kind::Metadata => {
|
||||||
signal_tx
|
signal_tx
|
||||||
@@ -426,13 +408,7 @@ async fn handle_nostr_notifications(
|
|||||||
.kind(Kind::InboxRelays)
|
.kind(Kind::InboxRelays)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if let Ok(output) = client.subscribe(filter, Some(opts)).await {
|
client.subscribe(filter, Some(opts)).await.ok();
|
||||||
log::info!(
|
|
||||||
"Subscribed to get DM relays: {} - Relays: {:?}",
|
|
||||||
event.pubkey.to_bech32().unwrap(),
|
|
||||||
output.success
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -515,10 +491,9 @@ async fn get_unwrapped(root: EventId) -> Result<Event, Error> {
|
|||||||
|
|
||||||
/// Unwraps a gift-wrapped event and processes its contents.
|
/// Unwraps a gift-wrapped event and processes its contents.
|
||||||
async fn try_unwrap_event(
|
async fn try_unwrap_event(
|
||||||
|
event: &Event,
|
||||||
signal_tx: &Sender<NostrSignal>,
|
signal_tx: &Sender<NostrSignal>,
|
||||||
mta_tx: &Sender<PublicKey>,
|
mta_tx: &Sender<PublicKey>,
|
||||||
event: &Event,
|
|
||||||
incoming: bool,
|
|
||||||
) -> bool {
|
) -> bool {
|
||||||
let client = nostr_client();
|
let client = nostr_client();
|
||||||
let mut is_cached = false;
|
let mut is_cached = false;
|
||||||
@@ -552,16 +527,13 @@ async fn try_unwrap_event(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get all pubkeys from the event
|
|
||||||
let all_pubkeys = event.all_pubkeys();
|
|
||||||
|
|
||||||
// Send all pubkeys to the metadata batch to sync data
|
// Send all pubkeys to the metadata batch to sync data
|
||||||
for public_key in all_pubkeys {
|
for public_key in event.all_pubkeys() {
|
||||||
mta_tx.send(public_key).await.ok();
|
mta_tx.send(public_key).await.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send a notify to GPUI if this is a new message
|
// Send a notify to GPUI if this is a new message
|
||||||
if incoming {
|
if starting_time() <= &event.created_at {
|
||||||
signal_tx.send(NostrSignal::GiftWrap(event)).await.ok();
|
signal_tx.send(NostrSignal::GiftWrap(event)).await.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use std::cell::RefCell;
|
use std::collections::{BTreeSet, HashMap};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -43,6 +42,9 @@ use ui::{
|
|||||||
|
|
||||||
mod subject;
|
mod subject;
|
||||||
|
|
||||||
|
const DUPLICATE_TIME_WINDOW: u64 = 10;
|
||||||
|
const MAX_RECENT_MESSAGES_TO_CHECK: usize = 5;
|
||||||
|
|
||||||
#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
|
#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
|
||||||
#[action(namespace = chat, no_json)]
|
#[action(namespace = chat, no_json)]
|
||||||
pub struct ChangeSubject(pub String);
|
pub struct ChangeSubject(pub String);
|
||||||
@@ -57,7 +59,7 @@ pub struct Chat {
|
|||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
// Chat Room
|
// Chat Room
|
||||||
room: Entity<Room>,
|
room: Entity<Room>,
|
||||||
messages: Entity<Vec<Rc<RefCell<Message>>>>,
|
messages: Entity<BTreeSet<Message>>,
|
||||||
text_data: HashMap<EventId, RichText>,
|
text_data: HashMap<EventId, RichText>,
|
||||||
list_state: ListState,
|
list_state: ListState,
|
||||||
// New Message
|
// New Message
|
||||||
@@ -76,7 +78,7 @@ impl Chat {
|
|||||||
pub fn new(room: Entity<Room>, window: &mut Window, cx: &mut App) -> Entity<Self> {
|
pub fn new(room: Entity<Room>, window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||||
let attaches = cx.new(|_| None);
|
let attaches = cx.new(|_| None);
|
||||||
let replies_to = cx.new(|_| None);
|
let replies_to = cx.new(|_| None);
|
||||||
let messages = cx.new(|_| vec![]);
|
let messages = cx.new(|_| BTreeSet::new());
|
||||||
|
|
||||||
let input = cx.new(|cx| {
|
let input = cx.new(|cx| {
|
||||||
InputState::new(window, cx)
|
InputState::new(window, cx)
|
||||||
@@ -120,10 +122,10 @@ impl Chat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let old_len = this.messages.read(cx).len();
|
let old_len = this.messages.read(cx).len();
|
||||||
let message = event.clone().into_rc();
|
let message = event.to_owned();
|
||||||
|
|
||||||
cx.update_entity(&this.messages, |this, cx| {
|
cx.update_entity(&this.messages, |this, cx| {
|
||||||
this.extend(vec![message]);
|
this.insert(message);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -132,7 +134,7 @@ impl Chat {
|
|||||||
RoomSignal::Refresh => {
|
RoomSignal::Refresh => {
|
||||||
this.load_messages(window, cx);
|
this.load_messages(window, cx);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -179,7 +181,7 @@ impl Chat {
|
|||||||
|
|
||||||
// Extend the messages list with the new events
|
// Extend the messages list with the new events
|
||||||
this.messages.update(cx, |this, cx| {
|
this.messages.update(cx, |this, cx| {
|
||||||
this.extend(messages.into_iter().map(|e| e.into_rc()));
|
this.extend(messages);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -237,14 +239,18 @@ impl Chat {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let min_timestamp = new_msg.created_at.as_u64().saturating_sub(10);
|
let messages = self.messages.read(cx);
|
||||||
|
let min_timestamp = new_msg
|
||||||
|
.created_at
|
||||||
|
.as_u64()
|
||||||
|
.saturating_sub(DUPLICATE_TIME_WINDOW);
|
||||||
|
|
||||||
self.messages
|
messages
|
||||||
.read(cx)
|
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|m| m.borrow().author == identity)
|
.rev()
|
||||||
|
.take(MAX_RECENT_MESSAGES_TO_CHECK)
|
||||||
|
.filter(|m| m.author == identity)
|
||||||
.any(|existing| {
|
.any(|existing| {
|
||||||
let existing = existing.borrow();
|
|
||||||
// Check if messages are within the time window
|
// Check if messages are within the time window
|
||||||
(existing.created_at.as_u64() >= min_timestamp) &&
|
(existing.created_at.as_u64() >= min_timestamp) &&
|
||||||
// Compare content and author
|
// Compare content and author
|
||||||
@@ -316,10 +322,9 @@ impl Chat {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.messages.update(cx, |this, cx| {
|
this.messages.update(cx, |this, cx| {
|
||||||
if let Some(msg) =
|
if let Some(mut msg) = this.iter().find(|msg| msg.id == id).cloned()
|
||||||
this.iter().find(|msg| msg.borrow().id == id).cloned()
|
|
||||||
{
|
{
|
||||||
msg.borrow_mut().errors = Some(reports);
|
msg.errors = Some(reports);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -334,10 +339,9 @@ impl Chat {
|
|||||||
|
|
||||||
fn insert_message(&self, message: Message, cx: &mut Context<Self>) {
|
fn insert_message(&self, message: Message, cx: &mut Context<Self>) {
|
||||||
let old_len = self.messages.read(cx).len();
|
let old_len = self.messages.read(cx).len();
|
||||||
let message = message.into_rc();
|
|
||||||
|
|
||||||
cx.update_entity(&self.messages, |this, cx| {
|
cx.update_entity(&self.messages, |this, cx| {
|
||||||
this.extend(vec![message]);
|
this.insert(message);
|
||||||
cx.notify();
|
cx.notify();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -345,12 +349,7 @@ impl Chat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_to(&self, id: EventId, cx: &Context<Self>) {
|
fn scroll_to(&self, id: EventId, cx: &Context<Self>) {
|
||||||
if let Some(ix) = self
|
if let Some(ix) = self.messages.read(cx).iter().position(|m| m.id == id) {
|
||||||
.messages
|
|
||||||
.read(cx)
|
|
||||||
.iter()
|
|
||||||
.position(|m| m.borrow().id == id)
|
|
||||||
{
|
|
||||||
self.list_state.scroll_to_reveal_item(ix);
|
self.list_state.scroll_to_reveal_item(ix);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,8 +358,9 @@ impl Chat {
|
|||||||
let Some(item) = self
|
let Some(item) = self
|
||||||
.messages
|
.messages
|
||||||
.read(cx)
|
.read(cx)
|
||||||
.get(ix)
|
.iter()
|
||||||
.map(|m| ClipboardItem::new_string(m.borrow().content.to_string()))
|
.nth(ix)
|
||||||
|
.map(|m| ClipboardItem::new_string(m.content.to_string()))
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -369,7 +369,7 @@ impl Chat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn reply_to(&mut self, ix: usize, cx: &mut Context<Self>) {
|
fn reply_to(&mut self, ix: usize, cx: &mut Context<Self>) {
|
||||||
let Some(message) = self.messages.read(cx).get(ix).map(|m| m.borrow().clone()) else {
|
let Some(message) = self.messages.read(cx).iter().nth(ix).map(|m| m.to_owned()) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -583,7 +583,7 @@ impl Chat {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) -> impl IntoElement {
|
) -> impl IntoElement {
|
||||||
let Some(message) = self.messages.read(cx).get(ix).map(|m| m.borrow()) else {
|
let Some(message) = self.messages.read(cx).iter().nth(ix) else {
|
||||||
return div().id(ix);
|
return div().id(ix);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -642,10 +642,7 @@ impl Chat {
|
|||||||
let messages = self.messages.read(cx);
|
let messages = self.messages.read(cx);
|
||||||
|
|
||||||
for (ix, id) in replies.iter().cloned().enumerate() {
|
for (ix, id) in replies.iter().cloned().enumerate() {
|
||||||
let Some(message) = messages
|
let Some(message) = messages.iter().find(|m| m.id == id)
|
||||||
.iter()
|
|
||||||
.map(|m| m.borrow())
|
|
||||||
.find(|m| m.id == id)
|
|
||||||
else {
|
else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use global::constants::{NEW_MESSAGE_ID, NIP17_RELAYS};
|
use global::constants::{GIFT_WRAP_SUB_ID, NIP17_RELAYS};
|
||||||
use global::nostr_client;
|
use global::nostr_client;
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
@@ -208,11 +208,8 @@ impl MessagingRelays {
|
|||||||
_ = client.connect_relay(&relay).await;
|
_ = client.connect_relay(&relay).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = SubscriptionId::new(NEW_MESSAGE_ID);
|
let id = SubscriptionId::new(GIFT_WRAP_SUB_ID);
|
||||||
let new_messages = Filter::new()
|
let new_messages = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
||||||
.kind(Kind::GiftWrap)
|
|
||||||
.pubkey(public_key)
|
|
||||||
.limit(0);
|
|
||||||
|
|
||||||
// Close old subscriptions
|
// Close old subscriptions
|
||||||
client.unsubscribe(&id).await;
|
client.unsubscribe(&id).await;
|
||||||
|
|||||||
@@ -36,20 +36,21 @@ pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nsec.app";
|
|||||||
/// Default timeout (in seconds) for Nostr Connect
|
/// Default timeout (in seconds) for Nostr Connect
|
||||||
pub const NOSTR_CONNECT_TIMEOUT: u64 = 200;
|
pub const NOSTR_CONNECT_TIMEOUT: u64 = 200;
|
||||||
|
|
||||||
/// Unique ID for new message subscription.
|
/// Unique ID for all gift wraps subscription.
|
||||||
pub const NEW_MESSAGE_ID: &str = "listen_new_giftwraps";
|
pub const GIFT_WRAP_SUB_ID: &str = "listen_for_giftwraps";
|
||||||
/// Unique ID for all messages subscription.
|
|
||||||
pub const ALL_MESSAGES_ID: &str = "listen_all_giftwraps";
|
|
||||||
/// Unique ID for all newest messages subscription.
|
|
||||||
pub const ALL_NEWEST_MESSAGES_ID: &str = "listen_all_newest_giftwraps";
|
|
||||||
|
|
||||||
/// Total metadata requests will be grouped.
|
/// Total metadata requests will be grouped.
|
||||||
pub const METADATA_BATCH_LIMIT: usize = 100;
|
pub const METADATA_BATCH_LIMIT: usize = 100;
|
||||||
|
|
||||||
/// Maximum timeout for grouping metadata requests.
|
/// Maximum timeout for grouping metadata requests.
|
||||||
pub const METADATA_BATCH_TIMEOUT: u64 = 400;
|
pub const METADATA_BATCH_TIMEOUT: u64 = 400;
|
||||||
|
|
||||||
|
/// Maximum timeout for waiting for finish (seconds)
|
||||||
|
pub const WAIT_FOR_FINISH: u64 = 60;
|
||||||
|
|
||||||
/// Default width for all modals.
|
/// Default width for all modals.
|
||||||
pub const DEFAULT_MODAL_WIDTH: f32 = 420.;
|
pub const DEFAULT_MODAL_WIDTH: f32 = 420.;
|
||||||
|
|
||||||
/// Default width of the sidebar.
|
/// Default width of the sidebar.
|
||||||
pub const DEFAULT_SIDEBAR_WIDTH: f32 = 280.;
|
pub const DEFAULT_SIDEBAR_WIDTH: f32 = 280.;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use nostr_connect::prelude::*;
|
|||||||
use nostr_sdk::prelude::*;
|
use nostr_sdk::prelude::*;
|
||||||
use paths::nostr_file;
|
use paths::nostr_file;
|
||||||
|
|
||||||
|
use crate::constants::GIFT_WRAP_SUB_ID;
|
||||||
use crate::paths::support_dir;
|
use crate::paths::support_dir;
|
||||||
|
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
@@ -33,6 +34,8 @@ pub enum NostrSignal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static NOSTR_CLIENT: OnceLock<Client> = OnceLock::new();
|
static NOSTR_CLIENT: OnceLock<Client> = OnceLock::new();
|
||||||
|
static GIFT_WRAP_ID: OnceLock<SubscriptionId> = OnceLock::new();
|
||||||
|
static CURRENT_TIMESTAMP: OnceLock<Timestamp> = OnceLock::new();
|
||||||
static FIRST_RUN: OnceLock<bool> = OnceLock::new();
|
static FIRST_RUN: OnceLock<bool> = OnceLock::new();
|
||||||
|
|
||||||
pub fn nostr_client() -> &'static Client {
|
pub fn nostr_client() -> &'static Client {
|
||||||
@@ -61,6 +64,14 @@ pub fn nostr_client() -> &'static Client {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn gift_wrap_sub_id() -> &'static SubscriptionId {
|
||||||
|
GIFT_WRAP_ID.get_or_init(|| SubscriptionId::new(GIFT_WRAP_SUB_ID))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn starting_time() -> &'static Timestamp {
|
||||||
|
CURRENT_TIMESTAMP.get_or_init(Timestamp::now)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn first_run() -> &'static bool {
|
pub fn first_run() -> &'static bool {
|
||||||
FIRST_RUN.get_or_init(|| {
|
FIRST_RUN.get_or_init(|| {
|
||||||
let flag = support_dir().join(format!(".{}-first_run", env!("CARGO_PKG_VERSION")));
|
let flag = support_dir().join(format!(".{}-first_run", env!("CARGO_PKG_VERSION")));
|
||||||
@@ -75,16 +86,3 @@ pub fn first_run() -> &'static bool {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_all_gift_wraps_fetched() {
|
|
||||||
let flag = support_dir().join(".fetched");
|
|
||||||
|
|
||||||
if !flag.exists() && smol::fs::write(&flag, "").await.is_err() {
|
|
||||||
log::error!("Failed to create full run flag");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_gift_wraps_fetch_complete() -> bool {
|
|
||||||
let flag = support_dir().join(".fetched");
|
|
||||||
flag.exists()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,11 +3,8 @@ use std::time::Duration;
|
|||||||
use anyhow::{anyhow, Error};
|
use anyhow::{anyhow, Error};
|
||||||
use client_keys::ClientKeys;
|
use client_keys::ClientKeys;
|
||||||
use common::handle_auth::CoopAuthUrlHandler;
|
use common::handle_auth::CoopAuthUrlHandler;
|
||||||
use global::constants::{
|
use global::constants::{ACCOUNT_D, NIP17_RELAYS, NIP65_RELAYS, NOSTR_CONNECT_TIMEOUT};
|
||||||
ACCOUNT_D, ALL_MESSAGES_ID, ALL_NEWEST_MESSAGES_ID, NEW_MESSAGE_ID, NIP17_RELAYS, NIP65_RELAYS,
|
use global::{gift_wrap_sub_id, nostr_client};
|
||||||
NOSTR_CONNECT_TIMEOUT,
|
|
||||||
};
|
|
||||||
use global::{is_gift_wraps_fetch_complete, nostr_client};
|
|
||||||
use gpui::prelude::FluentBuilder;
|
use gpui::prelude::FluentBuilder;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, red, App, AppContext, Context, Entity, Global, ParentElement, SharedString, Styled,
|
div, red, App, AppContext, Context, Entity, Global, ParentElement, SharedString, Styled,
|
||||||
@@ -632,13 +629,15 @@ impl Identity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn subscribe(client: &Client, public_key: PublicKey) -> Result<(), Error> {
|
pub(crate) async fn subscribe(client: &Client, public_key: PublicKey) -> Result<(), Error> {
|
||||||
let all_messages = SubscriptionId::new(ALL_MESSAGES_ID);
|
|
||||||
let all_newest_messages = SubscriptionId::new(ALL_NEWEST_MESSAGES_ID);
|
|
||||||
let new_messages = SubscriptionId::new(NEW_MESSAGE_ID);
|
|
||||||
// Subscription options
|
|
||||||
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
|
||||||
// Get the gift wraps fetch status
|
|
||||||
let is_completed = is_gift_wraps_fetch_complete();
|
client
|
||||||
|
.subscribe_with_id(
|
||||||
|
gift_wrap_sub_id().to_owned(),
|
||||||
|
Filter::new().kind(Kind::GiftWrap).pubkey(public_key),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
client
|
client
|
||||||
.subscribe(
|
.subscribe(
|
||||||
@@ -660,37 +659,6 @@ impl Identity {
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
client
|
|
||||||
.subscribe_with_id(
|
|
||||||
new_messages,
|
|
||||||
Filter::new()
|
|
||||||
.kind(Kind::GiftWrap)
|
|
||||||
.pubkey(public_key)
|
|
||||||
.limit(0),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if is_completed {
|
|
||||||
let week_ago: u64 = 7 * 24 * 60 * 60;
|
|
||||||
let since = Timestamp::from_secs(Timestamp::now().as_u64() - week_ago);
|
|
||||||
|
|
||||||
let filter = Filter::new()
|
|
||||||
.pubkey(public_key)
|
|
||||||
.kind(Kind::GiftWrap)
|
|
||||||
.since(since);
|
|
||||||
|
|
||||||
client
|
|
||||||
.subscribe_with_id(all_newest_messages, filter, Some(opts))
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
|
|
||||||
|
|
||||||
client
|
|
||||||
.subscribe_with_id(all_messages, filter, Some(opts))
|
|
||||||
.await?;
|
|
||||||
};
|
|
||||||
|
|
||||||
log::info!("Getting all user's metadata and messages...");
|
log::info!("Getting all user's metadata and messages...");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use std::cell::RefCell;
|
use std::hash::Hash;
|
||||||
use std::iter::IntoIterator;
|
use std::iter::IntoIterator;
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use chrono::{Local, TimeZone};
|
use chrono::{Local, TimeZone};
|
||||||
use gpui::SharedString;
|
use gpui::SharedString;
|
||||||
@@ -12,7 +11,7 @@ use crate::room::SendError;
|
|||||||
///
|
///
|
||||||
/// Contains information about the message content, author, creation time,
|
/// Contains information about the message content, author, creation time,
|
||||||
/// mentions, replies, and any errors that occurred during sending.
|
/// mentions, replies, and any errors that occurred during sending.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
/// Unique identifier of the message (EventId from nostr_sdk)
|
/// Unique identifier of the message (EventId from nostr_sdk)
|
||||||
pub id: EventId,
|
pub id: EventId,
|
||||||
@@ -30,6 +29,32 @@ pub struct Message {
|
|||||||
pub errors: Option<Vec<SendError>>,
|
pub errors: Option<Vec<SendError>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Eq for Message {}
|
||||||
|
|
||||||
|
impl PartialEq for Message {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id == other.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Message {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.created_at.cmp(&other.created_at)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Message {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for Message {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.id.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Builder pattern implementation for constructing Message objects.
|
/// Builder pattern implementation for constructing Message objects.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MessageBuilder {
|
pub struct MessageBuilder {
|
||||||
@@ -110,11 +135,6 @@ impl MessageBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds the message wrapped in an Rc<RefCell<Message>>
|
|
||||||
pub fn build_rc(self) -> Result<Rc<RefCell<Message>>, String> {
|
|
||||||
self.build().map(|m| Rc::new(RefCell::new(m)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds the message
|
/// Builds the message
|
||||||
pub fn build(self) -> Result<Message, String> {
|
pub fn build(self) -> Result<Message, String> {
|
||||||
Ok(Message {
|
Ok(Message {
|
||||||
@@ -135,16 +155,6 @@ impl Message {
|
|||||||
MessageBuilder::new(id, author)
|
MessageBuilder::new(id, author)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts the message into an Rc<RefCell<Message>>
|
|
||||||
pub fn into_rc(self) -> Rc<RefCell<Self>> {
|
|
||||||
Rc::new(RefCell::new(self))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds a message from a builder and wraps it in Rc<RefCell>
|
|
||||||
pub fn build_rc(builder: MessageBuilder) -> Result<Rc<RefCell<Self>>, String> {
|
|
||||||
builder.build().map(|m| Rc::new(RefCell::new(m)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a human-readable string representing how long ago the message was created
|
/// Returns a human-readable string representing how long ago the message was created
|
||||||
pub fn ago(&self) -> SharedString {
|
pub fn ago(&self) -> SharedString {
|
||||||
let input_time = match Local.timestamp_opt(self.created_at.as_u64() as i64, 0) {
|
let input_time = match Local.timestamp_opt(self.created_at.as_u64() as i64, 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user