chore: update gpui and nostr sdk

This commit is contained in:
2025-06-25 20:00:05 +07:00
parent edee9305cc
commit 3c2eaabab2
14 changed files with 209 additions and 295 deletions

371
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,16 +11,17 @@ publish = false
[workspace.dependencies] [workspace.dependencies]
coop = { path = "crates/*" } coop = { path = "crates/*" }
# UI # GPUI
gpui = { git = "https://github.com/zed-industries/zed" } gpui = { git = "https://github.com/zed-industries/zed" }
reqwest_client = { git = "https://github.com/zed-industries/zed" } reqwest_client = { git = "https://github.com/zed-industries/zed" }
# Nostr # Nostr
nostr = { git = "https://github.com/rust-nostr/nostr" } nostr = { git = "https://github.com/rust-nostr/nostr" }
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "nip96", "nip59", "nip49", "nip44", "nip05"] } nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "nip96", "nip59", "nip49", "nip44"] }
nostr-connect = { git = "https://github.com/rust-nostr/nostr" } nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
# Others # Others
reqwest = { version = "0.12", features = ["stream"] }
emojis = "0.6.4" emojis = "0.6.4"
smol = "2" smol = "2"
futures = "0.3" futures = "0.3"

View File

@@ -12,7 +12,7 @@ gpui.workspace = true
nostr-sdk.workspace = true nostr-sdk.workspace = true
anyhow.workspace = true anyhow.workspace = true
smol.workspace = true smol.workspace = true
reqwest.workspace = true
log.workspace = true log.workspace = true
tempfile = "3.19.1" tempfile = "3.19.1"
reqwest = { version = "0.12", features = ["stream"] }

View File

@@ -4,7 +4,6 @@ use std::sync::Arc;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use chrono::{Local, TimeZone}; use chrono::{Local, TimeZone};
use common::profile::RenderProfile; use common::profile::RenderProfile;
use common::{compare, room_hash};
use global::shared_state; use global::shared_state;
use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task, Window}; use gpui::{App, AppContext, Context, EventEmitter, SharedString, Task, Window};
use identity::Identity; use identity::Identity;
@@ -79,7 +78,7 @@ impl Room {
/// ///
/// A new Room instance with information extracted from the event /// A new Room instance with information extracted from the event
pub fn new(event: &Event) -> Self { pub fn new(event: &Event) -> Self {
let id = room_hash(event); let id = common::room_hash(event);
let created_at = event.created_at; let created_at = event.created_at;
// Get all pubkeys from the event's tags // Get all pubkeys from the event's tags
@@ -405,7 +404,7 @@ impl Room {
let mut other_pubkeys = ev.tags.public_keys().copied().collect::<Vec<_>>(); let mut other_pubkeys = ev.tags.public_keys().copied().collect::<Vec<_>>();
other_pubkeys.push(ev.pubkey); other_pubkeys.push(ev.pubkey);
// Check if the event is from a member of the room // Check if the event is from a member of the room
compare(&other_pubkeys, &pubkeys) common::compare(&other_pubkeys, &pubkeys)
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();

View File

@@ -10,12 +10,14 @@ global = { path = "../global" }
gpui.workspace = true gpui.workspace = true
nostr-connect.workspace = true nostr-connect.workspace = true
nostr-sdk.workspace = true nostr-sdk.workspace = true
nostr.workspace = true
anyhow.workspace = true anyhow.workspace = true
itertools.workspace = true itertools.workspace = true
chrono.workspace = true chrono.workspace = true
smallvec.workspace = true smallvec.workspace = true
smol.workspace = true smol.workspace = true
futures.workspace = true futures.workspace = true
reqwest.workspace = true
webbrowser = "1.0.4" webbrowser = "1.0.4"
qrcode-generator = "5.0.0" qrcode-generator = "5.0.0"

View File

@@ -2,23 +2,43 @@ use std::collections::HashSet;
use std::hash::{DefaultHasher, Hash, Hasher}; use std::hash::{DefaultHasher, Hash, Hasher};
use std::sync::Arc; use std::sync::Arc;
use anyhow::{anyhow, Error, Result};
use gpui::{Image, ImageFormat}; use gpui::{Image, ImageFormat};
use itertools::Itertools; use itertools::Itertools;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use qrcode_generator::QrCodeEcc; use qrcode_generator::QrCodeEcc;
use reqwest::Client as ReqClient;
pub mod debounced_delay; pub mod debounced_delay;
pub mod handle_auth; pub mod handle_auth;
pub mod profile; pub mod profile;
pub async fn nip96_upload( pub async fn verify_nip05(public_key: PublicKey, address: &str) -> Result<bool, Error> {
client: &Client, let req_client = ReqClient::new();
upload_to: Url, let address = Nip05Address::parse(address)?;
file: Vec<u8>, let res = req_client.get(address.url().to_string()).send().await?;
) -> anyhow::Result<Url, anyhow::Error> { let json: Value = res.json().await?;
let signer = client.signer().await?; let verify = nip05::verify_from_json(&public_key, &address, &json);
let config: ServerConfig = nip96::get_server_config(upload_to.to_owned(), None).await?; Ok(verify)
}
pub async fn nip05_profile(address: &str) -> Result<Nip05Profile, Error> {
let req_client = ReqClient::new();
let address = Nip05Address::parse(address)?;
let res = req_client.get(address.url().to_string()).send().await?;
let json: Value = res.json().await?;
if let Ok(profile) = Nip05Profile::from_json(&address, &json) {
Ok(profile)
} else {
Err(anyhow!("Failed to get NIP-05 profile"))
}
}
pub async fn nip96_upload(client: &Client, server: Url, file: Vec<u8>) -> Result<Url, Error> {
let signer = client.signer().await?;
let config = nip96::get_server_config(server.to_owned(), None).await?;
let url = nip96::upload_data(&signer, &config, file, None, None).await?; let url = nip96::upload_data(&signer, &config, file, None, None).await?;
Ok(url) Ok(url)
@@ -26,10 +46,10 @@ pub async fn nip96_upload(
pub fn room_hash(event: &Event) -> u64 { pub fn room_hash(event: &Event) -> u64 {
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
let mut pubkeys: Vec<&PublicKey> = vec![]; let mut pubkeys: Vec<PublicKey> = vec![];
// Add all public keys from event // Add all public keys from event
pubkeys.push(&event.pubkey); pubkeys.push(event.pubkey);
pubkeys.extend(event.tags.public_keys().collect::<Vec<_>>()); pubkeys.extend(event.tags.public_keys().collect::<Vec<_>>());
// Generate unique hash // Generate unique hash

View File

@@ -24,6 +24,7 @@ reqwest_client.workspace = true
nostr-connect.workspace = true nostr-connect.workspace = true
nostr-sdk.workspace = true nostr-sdk.workspace = true
nostr.workspace = true
anyhow.workspace = true anyhow.workspace = true
serde.workspace = true serde.workspace = true

View File

@@ -7,8 +7,8 @@ use global::constants::{DEFAULT_MODAL_WIDTH, DEFAULT_SIDEBAR_WIDTH};
use global::shared_state; use global::shared_state;
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
div, impl_internal_actions, px, relative, App, AppContext, Axis, Context, Entity, IntoElement, div, px, relative, Action, App, AppContext, Axis, Context, Entity, IntoElement, ParentElement,
ParentElement, Render, Styled, Subscription, Task, Window, Render, Styled, Subscription, Task, Window,
}; };
use identity::Identity; use identity::Identity;
use nostr_connect::prelude::*; use nostr_connect::prelude::*;
@@ -25,8 +25,6 @@ use ui::{ContextModal, IconName, Root, Sizable, StyledExt, TitleBar};
use crate::views::chat::{self, Chat}; use crate::views::chat::{self, Chat};
use crate::views::{login, new_account, onboarding, preferences, sidebar, startup, welcome}; use crate::views::{login, new_account, onboarding, preferences, sidebar, startup, welcome};
impl_internal_actions!(dock, [ToggleModal]);
pub fn init(window: &mut Window, cx: &mut App) -> Entity<ChatSpace> { pub fn init(window: &mut Window, cx: &mut App) -> Entity<ChatSpace> {
ChatSpace::new(window, cx) ChatSpace::new(window, cx)
} }
@@ -56,7 +54,8 @@ pub enum ModalKind {
SetupRelay, SetupRelay,
} }
#[derive(Clone, PartialEq, Eq, Deserialize)] #[derive(Action, Clone, PartialEq, Eq, Deserialize)]
#[action(namespace = modal, no_json)]
pub struct ToggleModal { pub struct ToggleModal {
pub modal: ModalKind, pub modal: ModalKind,
} }

View File

@@ -10,11 +10,11 @@ use common::profile::RenderProfile;
use global::shared_state; use global::shared_state;
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
div, img, impl_internal_actions, list, px, red, relative, rems, svg, white, AnyElement, App, div, img, list, px, red, relative, rems, svg, white, Action, AnyElement, App, AppContext,
AppContext, ClipboardItem, Context, Div, Element, Empty, Entity, EventEmitter, Flatten, ClipboardItem, Context, Div, Element, Empty, Entity, EventEmitter, Flatten, FocusHandle,
FocusHandle, Focusable, InteractiveElement, IntoElement, ListAlignment, ListState, ObjectFit, Focusable, InteractiveElement, IntoElement, ListAlignment, ListState, ObjectFit, ParentElement,
ParentElement, PathPromptOptions, Render, RetainAllImageCache, SharedString, PathPromptOptions, Render, RetainAllImageCache, SharedString, StatefulInteractiveElement,
StatefulInteractiveElement, Styled, StyledImage, Subscription, Window, Styled, StyledImage, Subscription, Window,
}; };
use identity::Identity; use identity::Identity;
use itertools::Itertools; use itertools::Itertools;
@@ -38,11 +38,10 @@ use ui::{
use crate::views::subject; use crate::views::subject;
#[derive(Clone, PartialEq, Eq, Deserialize)] #[derive(Action, Clone, PartialEq, Eq, Deserialize)]
#[action(namespace = chat, no_json)]
pub struct ChangeSubject(pub String); pub struct ChangeSubject(pub String);
impl_internal_actions!(chat, [ChangeSubject]);
pub fn init(room: Entity<Room>, window: &mut Window, cx: &mut App) -> Arc<Entity<Chat>> { pub fn init(room: Entity<Room>, window: &mut Window, cx: &mut App) -> Arc<Entity<Chat>> {
Arc::new(Chat::new(room, window, cx)) Arc::new(Chat::new(room, window, cx))
} }

View File

@@ -4,17 +4,17 @@ use std::time::Duration;
use anyhow::{anyhow, Error}; use anyhow::{anyhow, Error};
use chats::room::{Room, RoomKind}; use chats::room::{Room, RoomKind};
use chats::ChatRegistry; use chats::ChatRegistry;
use common::nip05_profile;
use common::profile::RenderProfile; use common::profile::RenderProfile;
use global::shared_state; use global::shared_state;
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
div, img, impl_internal_actions, px, red, relative, uniform_list, App, AppContext, Context, div, img, px, red, relative, uniform_list, App, AppContext, Context, Entity,
Entity, InteractiveElement, IntoElement, ParentElement, Render, SharedString, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
StatefulInteractiveElement, Styled, Subscription, Task, TextAlign, Window, StatefulInteractiveElement, Styled, Subscription, Task, TextAlign, Window,
}; };
use itertools::Itertools; use itertools::Itertools;
use nostr_sdk::prelude::*; use nostr_sdk::prelude::*;
use serde::Deserialize;
use settings::AppSettings; use settings::AppSettings;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use smol::Timer; use smol::Timer;
@@ -28,11 +28,6 @@ pub fn init(window: &mut Window, cx: &mut App) -> Entity<Compose> {
cx.new(|cx| Compose::new(window, cx)) cx.new(|cx| Compose::new(window, cx))
} }
#[derive(Clone, PartialEq, Eq, Deserialize)]
struct SelectContact(PublicKey);
impl_internal_actions!(contacts, [SelectContact]);
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct Contact { struct Contact {
profile: Profile, profile: Profile,
@@ -245,15 +240,14 @@ impl Compose {
let task: Task<Result<Contact, anyhow::Error>> = if content.contains("@") { let task: Task<Result<Contact, anyhow::Error>> = if content.contains("@") {
cx.background_spawn(async move { cx.background_spawn(async move {
let (tx, rx) = oneshot::channel::<Nip05Profile>(); let (tx, rx) = oneshot::channel::<Option<Nip05Profile>>();
nostr_sdk::async_utility::task::spawn(async move { nostr_sdk::async_utility::task::spawn(async move {
if let Ok(profile) = nip05::profile(&content, None).await { let profile = nip05_profile(&content).await.ok();
tx.send(profile).ok(); tx.send(profile).ok();
}
}); });
if let Ok(profile) = rx.await { if let Ok(Some(profile)) = rx.await {
let public_key = profile.public_key; let public_key = profile.public_key;
let metadata = shared_state() let metadata = shared_state()
.client() .client()

View File

@@ -7,6 +7,7 @@ use chats::room::{Room, RoomKind};
use chats::{ChatRegistry, RoomEmitter}; use chats::{ChatRegistry, RoomEmitter};
use common::debounced_delay::DebouncedDelay; use common::debounced_delay::DebouncedDelay;
use common::profile::RenderProfile; use common::profile::RenderProfile;
use common::verify_nip05;
use element::DisplayRoom; use element::DisplayRoom;
use global::constants::{DEFAULT_MODAL_WIDTH, SEARCH_RELAYS}; use global::constants::{DEFAULT_MODAL_WIDTH, SEARCH_RELAYS};
use global::shared_state; use global::shared_state;
@@ -184,13 +185,8 @@ impl Sidebar {
continue; continue;
}; };
let Ok(verified) = nip05::verify(&event.pubkey, target, None).await else { if !verify_nip05(event.pubkey, target).await.unwrap_or(false) {
// Skip if NIP-05 verification fails // Skip if NIP-05 is not valid or failed to verify
continue;
};
if !verified {
// Skip if NIP-05 is not valid
continue; continue;
}; };

View File

@@ -1,11 +1,11 @@
use gpui::{actions, impl_internal_actions}; use gpui::{actions, Action};
use serde::Deserialize; use serde::Deserialize;
#[derive(Clone, PartialEq, Eq, Deserialize)] #[derive(Clone, Action, PartialEq, Eq, Deserialize)]
#[action(namespace = list, no_json)]
pub struct Confirm { pub struct Confirm {
/// Is confirm with secondary. /// Is confirm with secondary.
pub secondary: bool, pub secondary: bool,
} }
actions!(list, [Cancel, SelectPrev, SelectNext]); actions!(list, [Cancel, SelectPrev, SelectNext]);
impl_internal_actions!(list, [Confirm]);

View File

@@ -2,9 +2,9 @@ use std::rc::Rc;
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
div, impl_internal_actions, px, App, AppContext, Corner, Element, InteractiveElement, div, px, Action, App, AppContext, Corner, Element, InteractiveElement, IntoElement,
IntoElement, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement, Styled, WeakEntity,
WeakEntity, Window, Window,
}; };
use serde::Deserialize; use serde::Deserialize;
use theme::ActiveTheme; use theme::ActiveTheme;
@@ -14,11 +14,10 @@ use crate::input::InputState;
use crate::popover::{Popover, PopoverContent}; use crate::popover::{Popover, PopoverContent};
use crate::Icon; use crate::Icon;
#[derive(PartialEq, Clone, Debug, Deserialize)] #[derive(Action, PartialEq, Clone, Debug, Deserialize)]
#[action(namespace = emoji, no_json)]
pub struct EmitEmoji(pub SharedString); pub struct EmitEmoji(pub SharedString);
impl_internal_actions!(emoji, [EmitEmoji]);
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct EmojiPicker { pub struct EmojiPicker {
icon: Option<Icon>, icon: Option<Icon>,

View File

@@ -4,8 +4,8 @@ use std::rc::Rc;
use gpui::prelude::FluentBuilder as _; use gpui::prelude::FluentBuilder as _;
use gpui::{ use gpui::{
actions, div, impl_internal_actions, point, px, App, AppContext, Bounds, ClipboardItem, actions, div, point, px, Action, App, AppContext, Bounds, ClipboardItem, Context,
Context, DefiniteLength, Entity, EntityInputHandler, EventEmitter, FocusHandle, Focusable, DefiniteLength, Entity, EntityInputHandler, EventEmitter, FocusHandle, Focusable,
InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton, MouseDownEvent, InteractiveElement as _, IntoElement, KeyBinding, KeyDownEvent, MouseButton, MouseDownEvent,
MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, ScrollHandle, MouseMoveEvent, MouseUpEvent, ParentElement as _, Pixels, Point, Render, ScrollHandle,
ScrollWheelEvent, SharedString, Styled as _, Subscription, UTF16Selection, Window, WrappedLine, ScrollWheelEvent, SharedString, Styled as _, Subscription, UTF16Selection, Window, WrappedLine,
@@ -24,14 +24,13 @@ use crate::history::History;
use crate::scroll::ScrollbarState; use crate::scroll::ScrollbarState;
use crate::Root; use crate::Root;
#[derive(Clone, PartialEq, Eq, Deserialize)] #[derive(Action, Clone, PartialEq, Eq, Deserialize)]
#[action(namespace = input, no_json)]
pub struct Enter { pub struct Enter {
/// Is confirm with secondary. /// Is confirm with secondary.
pub secondary: bool, pub secondary: bool,
} }
impl_internal_actions!(input, [Enter]);
actions!( actions!(
input, input,
[ [