feat: extended screening (#144)

* improve mutual contacts check

* .

* .
This commit is contained in:
reya
2025-09-08 17:11:29 +07:00
committed by GitHub
parent 71140beb52
commit d0f7a1abd3
6 changed files with 349 additions and 166 deletions

97
Cargo.lock generated
View File

@@ -994,7 +994,7 @@ dependencies = [
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
"windows-link 0.1.3",
]
[[package]]
@@ -1123,7 +1123,7 @@ dependencies = [
[[package]]
name = "collections"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d"
source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918"
dependencies = [
"indexmap",
"rustc-hash 2.1.1",
@@ -1564,7 +1564,7 @@ dependencies = [
[[package]]
name = "derive_refineable"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d"
source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918"
dependencies = [
"proc-macro2",
"quote",
@@ -2490,7 +2490,7 @@ dependencies = [
[[package]]
name = "gpui"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d"
source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918"
dependencies = [
"anyhow",
"as-raw-xcb-connection",
@@ -2584,7 +2584,7 @@ dependencies = [
[[package]]
name = "gpui_macros"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d"
source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -2596,7 +2596,7 @@ dependencies = [
[[package]]
name = "gpui_tokio"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d"
source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918"
dependencies = [
"anyhow",
"gpui",
@@ -2819,7 +2819,7 @@ dependencies = [
[[package]]
name = "http_client"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d"
source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918"
dependencies = [
"anyhow",
"bytes",
@@ -2839,7 +2839,7 @@ dependencies = [
[[package]]
name = "http_client_tls"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d"
source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918"
dependencies = [
"rustls",
"rustls-platform-verifier",
@@ -3397,9 +3397,9 @@ dependencies = [
[[package]]
name = "lebe"
version = "0.5.2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
[[package]]
name = "libc"
@@ -3636,7 +3636,7 @@ dependencies = [
[[package]]
name = "media"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d"
source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918"
dependencies = [
"anyhow",
"bindgen 0.71.1",
@@ -3884,7 +3884,7 @@ dependencies = [
[[package]]
name = "nostr"
version = "0.43.0"
source = "git+https://github.com/rust-nostr/nostr#1c66cfdc883e79ecc36e12c50bfce9e286f8c951"
source = "git+https://github.com/rust-nostr/nostr#53751050e317e1b6ab91b80b1dff4c867a519b38"
dependencies = [
"aes",
"base64",
@@ -3908,7 +3908,7 @@ dependencies = [
[[package]]
name = "nostr-connect"
version = "0.43.0"
source = "git+https://github.com/rust-nostr/nostr#1c66cfdc883e79ecc36e12c50bfce9e286f8c951"
source = "git+https://github.com/rust-nostr/nostr#53751050e317e1b6ab91b80b1dff4c867a519b38"
dependencies = [
"async-utility",
"nostr",
@@ -3920,7 +3920,7 @@ dependencies = [
[[package]]
name = "nostr-database"
version = "0.43.0"
source = "git+https://github.com/rust-nostr/nostr#1c66cfdc883e79ecc36e12c50bfce9e286f8c951"
source = "git+https://github.com/rust-nostr/nostr#53751050e317e1b6ab91b80b1dff4c867a519b38"
dependencies = [
"flatbuffers",
"lru",
@@ -3931,7 +3931,7 @@ dependencies = [
[[package]]
name = "nostr-lmdb"
version = "0.43.0"
source = "git+https://github.com/rust-nostr/nostr#1c66cfdc883e79ecc36e12c50bfce9e286f8c951"
source = "git+https://github.com/rust-nostr/nostr#53751050e317e1b6ab91b80b1dff4c867a519b38"
dependencies = [
"async-utility",
"flume",
@@ -3945,7 +3945,7 @@ dependencies = [
[[package]]
name = "nostr-relay-pool"
version = "0.43.0"
source = "git+https://github.com/rust-nostr/nostr#1c66cfdc883e79ecc36e12c50bfce9e286f8c951"
source = "git+https://github.com/rust-nostr/nostr#53751050e317e1b6ab91b80b1dff4c867a519b38"
dependencies = [
"async-utility",
"async-wsocket",
@@ -3962,7 +3962,7 @@ dependencies = [
[[package]]
name = "nostr-sdk"
version = "0.43.0"
source = "git+https://github.com/rust-nostr/nostr#1c66cfdc883e79ecc36e12c50bfce9e286f8c951"
source = "git+https://github.com/rust-nostr/nostr#53751050e317e1b6ab91b80b1dff4c867a519b38"
dependencies = [
"async-utility",
"nostr",
@@ -4751,9 +4751,9 @@ dependencies = [
[[package]]
name = "pxfm"
version = "0.1.22"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "376f733579ac4d3b9fbf0afca99bf8f6b698d541118affca554d0b86f73c2470"
checksum = "f55f4fedc84ed39cb7a489322318976425e42a147e2be79d8f878e2884f94e84"
dependencies = [
"num-traits",
]
@@ -5076,7 +5076,7 @@ dependencies = [
[[package]]
name = "refineable"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d"
source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918"
dependencies = [
"derive_refineable",
"workspace-hack",
@@ -5230,7 +5230,7 @@ dependencies = [
[[package]]
name = "reqwest_client"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d"
source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918"
dependencies = [
"anyhow",
"bytes",
@@ -5765,7 +5765,7 @@ checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749"
[[package]]
name = "semantic_version"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d"
source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918"
dependencies = [
"anyhow",
"serde",
@@ -6206,7 +6206,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "sum_tree"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d"
source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918"
dependencies = [
"arrayvec",
"log",
@@ -7236,7 +7236,7 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "util"
version = "0.1.0"
source = "git+https://github.com/zed-industries/zed#84f166fc85e1675fb76183ad6f212891d596c38d"
source = "git+https://github.com/zed-industries/zed#0ef7ee172fd8dd4d0638a9731140cb3a74344918"
dependencies = [
"anyhow",
"async-fs",
@@ -7707,11 +7707,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.10"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.60.2",
"windows-sys 0.61.0",
]
[[package]]
@@ -7739,7 +7739,7 @@ dependencies = [
"windows-collections",
"windows-core 0.61.2",
"windows-future",
"windows-link",
"windows-link 0.1.3",
"windows-numerics",
]
@@ -7785,7 +7785,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement 0.60.0",
"windows-interface 0.59.1",
"windows-link",
"windows-link 0.1.3",
"windows-result 0.3.4",
"windows-strings 0.4.2",
]
@@ -7797,7 +7797,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core 0.61.2",
"windows-link",
"windows-link 0.1.3",
"windows-threading",
]
@@ -7851,6 +7851,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
[[package]]
name = "windows-link"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
[[package]]
name = "windows-numerics"
version = "0.2.0"
@@ -7858,7 +7864,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core 0.61.2",
"windows-link",
"windows-link 0.1.3",
]
[[package]]
@@ -7878,7 +7884,7 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
dependencies = [
"windows-link",
"windows-link 0.1.3",
"windows-result 0.3.4",
"windows-strings 0.4.2",
]
@@ -7898,7 +7904,7 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link",
"windows-link 0.1.3",
]
[[package]]
@@ -7907,7 +7913,7 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319"
dependencies = [
"windows-link",
"windows-link 0.1.3",
]
[[package]]
@@ -7916,7 +7922,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
"windows-link",
"windows-link 0.1.3",
]
[[package]]
@@ -7964,6 +7970,15 @@ dependencies = [
"windows-targets 0.53.3",
]
[[package]]
name = "windows-sys"
version = "0.61.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
dependencies = [
"windows-link 0.2.0",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
@@ -8016,7 +8031,7 @@ version = "0.53.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
dependencies = [
"windows-link",
"windows-link 0.1.3",
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
@@ -8033,7 +8048,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
dependencies = [
"windows-link",
"windows-link 0.1.3",
]
[[package]]
@@ -8492,18 +8507,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524"
[[package]]
name = "zerocopy"
version = "0.8.26"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.26"
version = "0.8.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831"
dependencies = [
"proc-macro2",
"quote",

View File

@@ -221,12 +221,12 @@ impl ChatSpace {
let nip65 = Filter::new().kind(Kind::RelayList).author(public_key);
if client.database().count(nip65).await.unwrap_or(0) > 0 {
let nip17 = Filter::new().kind(Kind::InboxRelays).author(public_key);
let dm_relays = Filter::new().kind(Kind::InboxRelays).author(public_key);
match client.database().query(nip17).await {
match client.database().query(dm_relays).await {
Ok(events) => {
if let Some(event) = events.first_owned() {
let relay_urls = Self::extract_relay_list(&event);
let relay_urls = nip17::extract_relay_list(&event).collect_vec();
if relay_urls.is_empty() {
if !is_sent_signal {
@@ -374,9 +374,6 @@ impl ChatSpace {
let ingester = ingester();
let css = css();
let auto_close =
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let mut processed_events: HashSet<EventId> = HashSet::new();
let mut challenges: HashSet<Cow<'_, str>> = HashSet::new();
let mut notifications = client.notifications();
@@ -395,7 +392,6 @@ impl ChatSpace {
match event.kind {
Kind::RelayList => {
// Get metadata for event's pubkey that matches the current user's pubkey
if let Ok(true) = Self::is_self_event(&event).await {
// Fetch user's metadata event
Self::fetch_single_event(Kind::Metadata, event.pubkey).await;
@@ -409,10 +405,10 @@ impl ChatSpace {
}
Kind::InboxRelays => {
if let Ok(true) = Self::is_self_event(&event).await {
let relays: Vec<RelayUrl> = Self::extract_relay_list(&event);
let relays = nip17::extract_relay_list(&event).collect_vec();
if !relays.is_empty() {
for relay in relays.iter() {
for relay in relays.clone().into_iter() {
if client.add_relay(relay).await.is_err() {
let notice = Notice::RelayFailed(relay.clone());
ingester.send(Signal::Notice(notice)).await;
@@ -424,7 +420,7 @@ impl ChatSpace {
}
// Subscribe to gift wrap events only in the current user's NIP-17 relays
Self::fetch_gift_wrap(&relays, event.pubkey).await;
Self::fetch_gift_wrap(relays, event.pubkey).await;
}
}
}
@@ -437,7 +433,7 @@ impl ChatSpace {
Filter::new().limit(limit).authors(public_keys).kinds(kinds);
client
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(auto_close))
.subscribe_to(BOOTSTRAP_RELAYS, filter, css.auto_close_opts)
.await
.ok();
}
@@ -587,20 +583,6 @@ impl ChatSpace {
}
}
fn extract_relay_list(event: &Event) -> Vec<RelayUrl> {
event
.tags
.filter_standardized(TagKind::Relay)
.filter_map(|t| {
if let TagStandard::Relay(url) = t {
Some(url.to_owned())
} else {
None
}
})
.collect()
}
/// Checks if an event is belong to the current user
async fn is_self_event(event: &Event) -> Result<bool, Error> {
let client = nostr_client();
@@ -613,22 +595,21 @@ impl ChatSpace {
/// Fetches a single event by kind and public key
pub async fn fetch_single_event(kind: Kind, public_key: PublicKey) {
let client = nostr_client();
let auto_close =
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let css = css();
let filter = Filter::new().kind(kind).author(public_key).limit(1);
if let Err(e) = client.subscribe(filter, Some(auto_close)).await {
if let Err(e) = client.subscribe(filter, css.auto_close_opts).await {
log::info!("Failed to subscribe: {e}");
}
}
pub async fn fetch_gift_wrap(relays: &[RelayUrl], public_key: PublicKey) {
pub async fn fetch_gift_wrap(relays: Vec<&RelayUrl>, public_key: PublicKey) {
let client = nostr_client();
let sub_id = css().gift_wrap_sub_id.clone();
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
if client
.subscribe_with_id_to(relays.to_owned(), sub_id, filter, None)
.subscribe_with_id_to(relays.clone(), sub_id, filter, None)
.await
.is_ok()
{
@@ -639,8 +620,7 @@ impl ChatSpace {
/// Fetches NIP-65 relay list for a given public key
pub async fn fetch_nip65_relays(public_key: PublicKey) -> Result<(), Error> {
let client = nostr_client();
let auto_close =
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let css = css();
let filter = Filter::new()
.kind(Kind::RelayList)
@@ -648,7 +628,7 @@ impl ChatSpace {
.limit(1);
client
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(auto_close))
.subscribe_to(BOOTSTRAP_RELAYS, filter, css.auto_close_opts)
.await?;
Ok(())
@@ -656,18 +636,15 @@ impl ChatSpace {
/// Fetches metadata for a list of public keys
async fn fetch_metadata_for_pubkeys(public_keys: HashSet<PublicKey>) {
if public_keys.is_empty() {
return;
};
let client = nostr_client();
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let kinds = vec![Kind::Metadata, Kind::ContactList];
let limit = public_keys.len() * kinds.len();
let css = css();
let kinds = vec![Kind::Metadata, Kind::ContactList, Kind::RelayList];
let limit = public_keys.len() * kinds.len() + 20; // + 20 to ensure Coop has enough metadata
let filter = Filter::new().limit(limit).authors(public_keys).kinds(kinds);
client
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts))
.subscribe_to(BOOTSTRAP_RELAYS, filter, css.auto_close_opts)
.await
.ok();
}

View File

@@ -1,9 +1,13 @@
use std::time::Duration;
use common::display::{shorten_pubkey, ReadableProfile};
use common::nip05::nip05_verify;
use global::constants::BOOTSTRAP_RELAYS;
use global::nostr_client;
use gpui::prelude::FluentBuilder;
use gpui::{
div, relative, rems, App, AppContext, Context, Div, Entity, IntoElement, ParentElement, Render,
SharedString, Styled, Task, Window,
div, px, relative, rems, uniform_list, App, AppContext, Context, Div, Entity,
InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Task, Window,
};
use gpui_tokio::Tokio;
use i18n::{shared_t, t};
@@ -14,6 +18,7 @@ use smallvec::{smallvec, SmallVec};
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonRounded, ButtonVariants};
use ui::indicator::Indicator;
use ui::{h_flex, v_flex, ContextModal, Icon, IconName, Sizable, StyledExt};
pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity<Screening> {
@@ -24,9 +29,10 @@ pub struct Screening {
profile: Profile,
verified: bool,
followed: bool,
dm_relays: bool,
mutual_contacts: usize,
_tasks: SmallVec<[Task<()>; 1]>,
dm_relays: Option<bool>,
active: Option<bool>,
mutual_contacts: Vec<Profile>,
_tasks: SmallVec<[Task<()>; 4]>,
}
impl Screening {
@@ -37,33 +43,69 @@ impl Screening {
let mut tasks = smallvec![];
let check_trust_score: Task<(bool, usize, bool)> = cx.background_spawn(async move {
let contact_check: Task<(bool, Vec<Profile>)> = cx.background_spawn(async move {
let client = nostr_client();
let follow = Filter::new()
.kind(Kind::ContactList)
.author(identity)
.pubkey(public_key)
// Check if user is in contact list
let contacts = client.database().contacts_public_keys(identity).await;
let followed = contacts.unwrap_or_default().contains(&public_key);
// Check mutual contacts
let contact_list = Filter::new().kind(Kind::ContactList).pubkey(public_key);
let mut mutual_contacts = vec![];
if let Ok(events) = client.database().query(contact_list).await {
for event in events.into_iter().filter(|ev| ev.pubkey != identity) {
if let Ok(metadata) = client.database().metadata(event.pubkey).await {
let profile = Profile::new(event.pubkey, metadata.unwrap_or_default());
mutual_contacts.push(profile);
}
}
}
(followed, mutual_contacts)
});
let activity_check = cx.background_spawn(async move {
let client = nostr_client();
let mut activity = false;
let filter = Filter::new()
.author(public_key)
.since(Timestamp::now() - Duration::from_secs(172800))
.limit(1);
let contacts = Filter::new()
.kind(Kind::ContactList)
.pubkey(public_key)
.limit(1);
if let Ok(mut stream) = client
.stream_events_from(BOOTSTRAP_RELAYS, filter, Duration::from_secs(2))
.await
{
while stream.next().await.is_some() {
activity = true
}
}
let relays = Filter::new()
activity
});
let relay_check = cx.background_spawn(async move {
let client = nostr_client();
let mut relay = false;
let filter = Filter::new()
.kind(Kind::InboxRelays)
.author(public_key)
.limit(1);
let is_follow = client.database().count(follow).await.unwrap_or(0) >= 1;
let mutual_contacts = client.database().count(contacts).await.unwrap_or(0);
let dm_relays = client.database().count(relays).await.unwrap_or(0) >= 1;
if let Ok(mut stream) = client.stream_events(filter, Duration::from_secs(2)).await {
while stream.next().await.is_some() {
relay = true
}
}
(is_follow, mutual_contacts, dm_relays)
relay
});
let verify_nip05 = if let Some(address) = profile.metadata().nip05 {
let addr_check = if let Some(address) = profile.metadata().nip05 {
Some(Tokio::spawn(cx, async move {
nip05_verify(public_key, &address).await.unwrap_or(false)
}))
@@ -72,20 +114,49 @@ impl Screening {
};
tasks.push(
// Load all necessary data
// Run the contact check in the background
cx.spawn_in(window, async move |this, cx| {
let (followed, mutual_contacts, dm_relays) = check_trust_score.await;
let (followed, mutual_contacts) = contact_check.await;
this.update(cx, |this, cx| {
this.followed = followed;
this.mutual_contacts = mutual_contacts;
this.dm_relays = dm_relays;
cx.notify();
})
.ok();
}),
);
// Update the NIP05 verification status if user has NIP05 address
if let Some(task) = verify_nip05 {
tasks.push(
// Run the activity check in the background
cx.spawn_in(window, async move |this, cx| {
let active = activity_check.await;
this.update(cx, |this, cx| {
this.active = Some(active);
cx.notify();
})
.ok();
}),
);
tasks.push(
// Run the relay check in the background
cx.spawn_in(window, async move |this, cx| {
let relay = relay_check.await;
this.update(cx, |this, cx| {
this.dm_relays = Some(relay);
cx.notify();
})
.ok();
}),
);
tasks.push(
// Run the NIP-05 verification in the background
cx.spawn_in(window, async move |this, cx| {
if let Some(task) = addr_check {
if let Ok(verified) = task.await {
this.update(cx, |this, cx| {
this.verified = verified;
@@ -101,8 +172,9 @@ impl Screening {
profile,
verified: false,
followed: false,
dm_relays: false,
mutual_contacts: 0,
dm_relays: None,
active: None,
mutual_contacts: vec![],
_tasks: tasks,
}
}
@@ -141,12 +213,54 @@ impl Screening {
})
.detach();
}
fn mutual_contacts(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let contacts = self.mutual_contacts.clone();
window.open_modal(cx, move |this, _window, _cx| {
let contacts = contacts.clone();
let total = contacts.len();
this.title(shared_t!("screening.mutual_label")).child(
v_flex().gap_1().pb_4().child(
uniform_list("contacts", total, move |range, _window, cx| {
let mut items = Vec::with_capacity(total);
for ix in range {
if let Some(contact) = contacts.get(ix) {
items.push(
h_flex()
.h_11()
.w_full()
.px_2()
.gap_1p5()
.rounded(cx.theme().radius)
.text_sm()
.hover(|this| {
this.bg(cx.theme().elevated_surface_background)
})
.child(
Avatar::new(contact.avatar_url(true)).size(rems(1.75)),
)
.child(contact.display_name()),
);
}
}
items
})
.h(px(300.)),
),
)
});
}
}
impl Render for Screening {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let proxy = AppSettings::get_proxy_user_avatars(cx);
let shorten_pubkey = shorten_pubkey(self.profile.public_key(), 8);
let total_mutuals = self.mutual_contacts.len();
v_flex()
.gap_4()
@@ -168,12 +282,10 @@ impl Render for Screening {
h_flex()
.gap_3()
.child(
div()
h_flex()
.p_1()
.flex_1()
.h_7()
.flex()
.items_center()
.justify_center()
.rounded_full()
.bg(cx.theme().surface_background)
@@ -217,25 +329,54 @@ impl Render for Screening {
.items_start()
.gap_2()
.text_sm()
.child(status_badge(self.followed, cx))
.child(status_badge(Some(self.followed), cx))
.child(
v_flex()
.text_sm()
.child(shared_t!("screening.contact_label"))
.child(div().text_color(cx.theme().text_muted).child({
if self.followed {
shared_t!("screening.contact")
} else {
shared_t!("screening.not_contact")
}
})),
.child(
div()
.line_clamp(1)
.text_color(cx.theme().text_muted)
.child({
if self.followed {
shared_t!("screening.contact")
} else {
shared_t!("screening.not_contact")
}
}),
),
),
)
.child(
h_flex()
.items_start()
.gap_2()
.child(status_badge(self.verified, cx))
.text_sm()
.child(status_badge(self.active, cx))
.child(
v_flex()
.text_sm()
.child(shared_t!("screening.active_label"))
.child(
div()
.line_clamp(1)
.text_color(cx.theme().text_muted)
.child({
if self.active == Some(true) {
shared_t!("screening.active")
} else {
shared_t!("screening.no_active")
}
}),
),
),
)
.child(
h_flex()
.items_start()
.gap_2()
.child(status_badge(Some(self.verified), cx))
.child(
v_flex()
.text_sm()
@@ -246,35 +387,61 @@ impl Render for Screening {
shared_t!("screening.nip05_label")
}
})
.child(div().text_color(cx.theme().text_muted).child({
if self.address(cx).is_some() {
if self.verified {
shared_t!("screening.nip05_ok")
} else {
shared_t!("screening.nip05_failed")
}
} else {
shared_t!("screening.nip05_empty")
}
})),
.child(
div()
.line_clamp(1)
.text_color(cx.theme().text_muted)
.child({
if self.address(cx).is_some() {
if self.verified {
shared_t!("screening.nip05_ok")
} else {
shared_t!("screening.nip05_failed")
}
} else {
shared_t!("screening.nip05_empty")
}
}),
),
),
)
.child(
h_flex()
.items_start()
.gap_2()
.child(status_badge(self.mutual_contacts > 0, cx))
.child(status_badge(Some(total_mutuals > 0), cx))
.child(
v_flex()
.text_sm()
.child(shared_t!("screening.mutual_label"))
.child(div().text_color(cx.theme().text_muted).child({
if self.mutual_contacts > 0 {
shared_t!("screening.mutual", u = self.mutual_contacts)
} else {
shared_t!("screening.no_mutual")
}
})),
.child(
h_flex()
.gap_0p5()
.child(shared_t!("screening.mutual_label"))
.child(
Button::new("mutuals")
.icon(IconName::Info)
.xsmall()
.ghost()
.rounded(ButtonRounded::Full)
.on_click(cx.listener(
move |this, _, window, cx| {
this.mutual_contacts(window, cx);
},
)),
),
)
.child(
div()
.line_clamp(1)
.text_color(cx.theme().text_muted)
.child({
if total_mutuals > 0 {
shared_t!("screening.mutual", u = total_mutuals)
} else {
shared_t!("screening.no_mutual")
}
}),
),
),
)
.child(
@@ -287,36 +454,43 @@ impl Render for Screening {
.w_full()
.text_sm()
.child({
if self.dm_relays {
if self.dm_relays == Some(true) {
shared_t!("screening.relay_found")
} else {
shared_t!("screening.relay_empty")
}
})
.child(div().w_full().text_color(cx.theme().text_muted).child(
{
if self.dm_relays {
shared_t!("screening.relay_found_desc")
} else {
shared_t!("screening.relay_empty_desc")
}
},
)),
.child(
div()
.w_full()
.line_clamp(1)
.text_color(cx.theme().text_muted)
.child({
if self.dm_relays == Some(true) {
shared_t!("screening.relay_found_desc")
} else {
shared_t!("screening.relay_empty_desc")
}
}),
),
),
),
)
}
}
fn status_badge(status: bool, cx: &App) -> Div {
div()
.pt_1()
.flex_shrink_0()
.child(Icon::new(IconName::CheckCircleFill).small().text_color({
if status {
cx.theme().icon_accent
} else {
cx.theme().icon_muted
}
}))
fn status_badge(status: Option<bool>, cx: &App) -> Div {
div().pt_1().flex_shrink_0().map(|this| {
if let Some(status) = status {
this.child(Icon::new(IconName::CheckCircleFill).small().text_color({
if status {
cx.theme().icon_accent
} else {
cx.theme().icon_muted
}
}))
} else {
this.child(Indicator::new().xsmall())
}
})
}

View File

@@ -2,7 +2,7 @@ use std::time::Duration;
use anyhow::{anyhow, Error};
use global::constants::NIP17_RELAYS;
use global::nostr_client;
use global::{css, nostr_client};
use gpui::prelude::FluentBuilder;
use gpui::{
div, px, uniform_list, App, AppContext, Context, Entity, InteractiveElement, IntoElement,
@@ -19,8 +19,6 @@ use ui::input::{InputEvent, InputState, TextInput};
use ui::modal::ModalButtonProps;
use ui::{h_flex, v_flex, ContextModal, IconName, Sizable, StyledExt};
use crate::chatspace::ChatSpace;
pub fn init(kind: Kind, window: &mut Window, cx: &mut App) -> Entity<SetupRelay> {
cx.new(|cx| SetupRelay::new(kind, window, cx))
}
@@ -220,7 +218,16 @@ impl SetupRelay {
}
// Fetch gift wrap events
ChatSpace::fetch_gift_wrap(&relays, public_key).await;
let sub_id = css().gift_wrap_sub_id.clone();
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
if client
.subscribe_with_id_to(relays.clone(), sub_id, filter, None)
.await
.is_ok()
{
log::info!("Subscribed to messages in: {relays:?}");
};
Ok(())
});

View File

@@ -119,6 +119,7 @@ pub struct CoopSimpleStorage {
pub init_at: Timestamp,
pub gift_wrap_sub_id: SubscriptionId,
pub gift_wrap_processing: AtomicBool,
pub auto_close_opts: Option<SubscribeAutoCloseOptions>,
pub sent_ids: RwLock<HashSet<EventId>>,
pub resent_ids: RwLock<Vec<Output<EventId>>>,
pub resend_queue: RwLock<HashMap<EventId, RelayUrl>>,
@@ -136,6 +137,9 @@ impl CoopSimpleStorage {
init_at: Timestamp::now(),
gift_wrap_sub_id: SubscriptionId::new("inbox"),
gift_wrap_processing: AtomicBool::new(false),
auto_close_opts: Some(
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE),
),
sent_ids: RwLock::new(HashSet::new()),
resent_ids: RwLock::new(Vec::new()),
resend_queue: RwLock::new(HashMap::new()),

View File

@@ -219,6 +219,12 @@ screening:
en: "This person is one of your contacts."
not_contact:
en: "This person is not one of your contacts."
active_label:
en: "Activity"
active:
en: "This person has activity in the last 2 days"
no_active:
en: "This person has no activity in the last 2 days"
mutual_label:
en: "Mutual contacts"
mutual: