wip: command bar
Some checks failed
Rust / build (ubuntu-latest, stable) (push) Failing after 1m47s
Rust / build (ubuntu-latest, stable) (pull_request) Failing after 1m58s

This commit is contained in:
2026-02-03 16:34:47 +07:00
parent f164e86967
commit 89fd00ddef
7 changed files with 456 additions and 148 deletions

View File

@@ -19,5 +19,7 @@ flume.workspace = true
log.workspace = true
anyhow.workspace = true
webbrowser.workspace = true
serde.workspace = true
serde_json.workspace = true
rustls = "0.23"

View File

@@ -3,7 +3,7 @@ use std::os::unix::fs::PermissionsExt;
use std::time::Duration;
use anyhow::{anyhow, Error};
use common::{config_dir, BOOTSTRAP_RELAYS, CLIENT_NAME, SEARCH_RELAYS};
use common::{config_dir, CLIENT_NAME};
use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task};
use nostr_connect::prelude::*;
use nostr_lmdb::NostrLmdb;
@@ -37,6 +37,17 @@ pub const NOSTR_CONNECT_RELAY: &str = "wss://relay.nsec.app";
pub const DEVICE_GIFTWRAP: &str = "device-gift-wraps";
/// Default subscription id for user gift wrap events
pub const USER_GIFTWRAP: &str = "user-gift-wraps";
/// Default vertex relays
pub const WOT_RELAYS: [&str; 1] = ["wss://relay.vertexlab.io"];
/// Default search relays
pub const SEARCH_RELAYS: [&str; 2] = ["wss://search.nos.today", "wss://relay.noswhere.com"];
/// Default bootstrap relays
pub const BOOTSTRAP_RELAYS: [&str; 4] = [
"wss://relay.damus.io",
"wss://relay.primal.net",
"wss://relay.nos.social",
"wss://user.kindpag.es",
];
pub fn init(cx: &mut App) {
// Initialize the tokio runtime
@@ -214,6 +225,11 @@ impl NostrRegistry {
client.add_relay(url).await?;
}
// Add wot relay to the relay pool
for url in WOT_RELAYS.into_iter() {
client.add_relay(url).await?;
}
// Connect to all added relays
client.connect().await;
@@ -647,6 +663,19 @@ impl NostrRegistry {
}));
}
/// Get contact list for the current user
pub fn get_contact_list(&self, cx: &App) -> Task<Result<Vec<PublicKey>, Error>> {
let client = self.client();
let public_key = self.identity().read(cx).public_key();
cx.background_spawn(async move {
let contacts = client.database().contacts_public_keys(public_key).await?;
let results = contacts.into_iter().collect();
Ok(results)
})
}
/// Set the metadata for the current user
pub fn set_metadata(&self, metadata: &Metadata, cx: &App) -> Task<Result<(), Error>> {
let client = self.client();
@@ -806,13 +835,41 @@ impl NostrRegistry {
(signer, uri)
}
/// Get the public key of a NIP-05 address
pub fn get_address(&self, addr: Nip05Address, cx: &App) -> Task<Result<PublicKey, Error>> {
let client = self.client();
let http_client = cx.http_client();
cx.background_spawn(async move {
let profile = addr.profile(&http_client).await?;
let public_key = profile.public_key;
let opts = SubscribeAutoCloseOptions::default()
.exit_policy(ReqExitPolicy::ExitOnEOSE)
.timeout(Some(Duration::from_secs(3)));
// Construct the filter for the metadata event
let filter = Filter::new()
.kind(Kind::Metadata)
.author(public_key)
.limit(1);
// Subscribe to bootstrap relays
client
.subscribe_to(BOOTSTRAP_RELAYS, vec![filter], Some(opts))
.await?;
Ok(public_key)
})
}
/// Perform a NIP-50 global search for user profiles based on a given query
pub fn search(&self, query: &str, cx: &App) -> Task<Result<Vec<Event>, Error>> {
pub fn search(&self, query: &str, cx: &App) -> Task<Result<Vec<PublicKey>, Error>> {
let client = self.client();
let query = query.to_string();
cx.background_spawn(async move {
let mut results: Vec<Event> = Vec::with_capacity(FIND_LIMIT);
let mut results: Vec<PublicKey> = Vec::with_capacity(FIND_LIMIT);
// Construct the filter for the search query
let filter = Filter::new()
@@ -828,7 +885,7 @@ impl NostrRegistry {
// Collect the results
while let Some((_url, res)) = stream.next().await {
if let Ok(event) = res {
results.push(event);
results.push(event.pubkey);
}
}
@@ -839,6 +896,64 @@ impl NostrRegistry {
Ok(results)
})
}
/// Perform a WoT (via Vertex) search for a given query.
pub fn wot_search(&self, query: &str, cx: &App) -> Task<Result<Vec<PublicKey>, Error>> {
let client = self.client();
let query = query.to_string();
cx.background_spawn(async move {
let signer = client.signer().await?;
// Construct a vertex request event
let event = EventBuilder::new(Kind::Custom(5315), "")
.tags(vec![
Tag::custom(TagKind::custom("param"), vec!["search", &query]),
Tag::custom(TagKind::custom("param"), vec!["limit", "10"]),
])
.sign(&signer)
.await?;
// Send the event to vertex relays
let output = client.send_event_to(WOT_RELAYS, &event).await?;
// Construct a filter to get the response or error from vertex
let filter = Filter::new()
.kinds(vec![Kind::Custom(6315), Kind::Custom(7000)])
.event(output.id().to_owned());
// Stream events from the search relays
let mut stream = client
.stream_events_from(WOT_RELAYS, vec![filter], Duration::from_secs(3))
.await?;
while let Some((_url, res)) = stream.next().await {
if let Ok(event) = res {
match event.kind {
Kind::Custom(6315) => {
let content: serde_json::Value = serde_json::from_str(&event.content)?;
let pubkeys: Vec<PublicKey> = content
.as_array()
.into_iter()
.flatten()
.filter_map(|item| item.as_object())
.filter_map(|obj| obj.get("pubkey").and_then(|v| v.as_str()))
.filter_map(|pubkey_str| PublicKey::parse(pubkey_str).ok())
.collect();
return Ok(pubkeys);
}
Kind::Custom(7000) => {
return Err(anyhow!("Search error"));
}
_ => {}
}
}
}
Err(anyhow!("No results for query: {query}"))
})
}
}
#[derive(Debug, Clone)]