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

View File

@@ -221,12 +221,12 @@ impl ChatSpace {
let nip65 = Filter::new().kind(Kind::RelayList).author(public_key); let nip65 = Filter::new().kind(Kind::RelayList).author(public_key);
if client.database().count(nip65).await.unwrap_or(0) > 0 { 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) => { Ok(events) => {
if let Some(event) = events.first_owned() { 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 relay_urls.is_empty() {
if !is_sent_signal { if !is_sent_signal {
@@ -374,9 +374,6 @@ impl ChatSpace {
let ingester = ingester(); let ingester = ingester();
let css = css(); let css = css();
let auto_close =
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let mut processed_events: HashSet<EventId> = HashSet::new(); let mut processed_events: HashSet<EventId> = HashSet::new();
let mut challenges: HashSet<Cow<'_, str>> = HashSet::new(); let mut challenges: HashSet<Cow<'_, str>> = HashSet::new();
let mut notifications = client.notifications(); let mut notifications = client.notifications();
@@ -395,7 +392,6 @@ impl ChatSpace {
match event.kind { match event.kind {
Kind::RelayList => { 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 { if let Ok(true) = Self::is_self_event(&event).await {
// Fetch user's metadata event // Fetch user's metadata event
Self::fetch_single_event(Kind::Metadata, event.pubkey).await; Self::fetch_single_event(Kind::Metadata, event.pubkey).await;
@@ -409,10 +405,10 @@ impl ChatSpace {
} }
Kind::InboxRelays => { Kind::InboxRelays => {
if let Ok(true) = Self::is_self_event(&event).await { 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() { if !relays.is_empty() {
for relay in relays.iter() { for relay in relays.clone().into_iter() {
if client.add_relay(relay).await.is_err() { if client.add_relay(relay).await.is_err() {
let notice = Notice::RelayFailed(relay.clone()); let notice = Notice::RelayFailed(relay.clone());
ingester.send(Signal::Notice(notice)).await; 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 // 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); Filter::new().limit(limit).authors(public_keys).kinds(kinds);
client client
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(auto_close)) .subscribe_to(BOOTSTRAP_RELAYS, filter, css.auto_close_opts)
.await .await
.ok(); .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 /// Checks if an event is belong to the current user
async fn is_self_event(event: &Event) -> Result<bool, Error> { async fn is_self_event(event: &Event) -> Result<bool, Error> {
let client = nostr_client(); let client = nostr_client();
@@ -613,22 +595,21 @@ impl ChatSpace {
/// Fetches a single event by kind and public key /// Fetches a single event by kind and public key
pub async fn fetch_single_event(kind: Kind, public_key: PublicKey) { pub async fn fetch_single_event(kind: Kind, public_key: PublicKey) {
let client = nostr_client(); let client = nostr_client();
let auto_close = let css = css();
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let filter = Filter::new().kind(kind).author(public_key).limit(1); 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}"); 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 client = nostr_client();
let sub_id = css().gift_wrap_sub_id.clone(); let sub_id = css().gift_wrap_sub_id.clone();
let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key); let filter = Filter::new().kind(Kind::GiftWrap).pubkey(public_key);
if client if client
.subscribe_with_id_to(relays.to_owned(), sub_id, filter, None) .subscribe_with_id_to(relays.clone(), sub_id, filter, None)
.await .await
.is_ok() .is_ok()
{ {
@@ -639,8 +620,7 @@ impl ChatSpace {
/// Fetches NIP-65 relay list for a given public key /// Fetches NIP-65 relay list for a given public key
pub async fn fetch_nip65_relays(public_key: PublicKey) -> Result<(), Error> { pub async fn fetch_nip65_relays(public_key: PublicKey) -> Result<(), Error> {
let client = nostr_client(); let client = nostr_client();
let auto_close = let css = css();
SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE);
let filter = Filter::new() let filter = Filter::new()
.kind(Kind::RelayList) .kind(Kind::RelayList)
@@ -648,7 +628,7 @@ impl ChatSpace {
.limit(1); .limit(1);
client client
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(auto_close)) .subscribe_to(BOOTSTRAP_RELAYS, filter, css.auto_close_opts)
.await?; .await?;
Ok(()) Ok(())
@@ -656,18 +636,15 @@ impl ChatSpace {
/// Fetches metadata for a list of public keys /// Fetches metadata for a list of public keys
async fn fetch_metadata_for_pubkeys(public_keys: HashSet<PublicKey>) { async fn fetch_metadata_for_pubkeys(public_keys: HashSet<PublicKey>) {
if public_keys.is_empty() {
return;
};
let client = nostr_client(); let client = nostr_client();
let opts = SubscribeAutoCloseOptions::default().exit_policy(ReqExitPolicy::ExitOnEOSE); let css = css();
let kinds = vec![Kind::Metadata, Kind::ContactList];
let limit = public_keys.len() * kinds.len(); 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); let filter = Filter::new().limit(limit).authors(public_keys).kinds(kinds);
client client
.subscribe_to(BOOTSTRAP_RELAYS, filter, Some(opts)) .subscribe_to(BOOTSTRAP_RELAYS, filter, css.auto_close_opts)
.await .await
.ok(); .ok();
} }

View File

@@ -1,9 +1,13 @@
use std::time::Duration;
use common::display::{shorten_pubkey, ReadableProfile}; use common::display::{shorten_pubkey, ReadableProfile};
use common::nip05::nip05_verify; use common::nip05::nip05_verify;
use global::constants::BOOTSTRAP_RELAYS;
use global::nostr_client; use global::nostr_client;
use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
div, relative, rems, App, AppContext, Context, Div, Entity, IntoElement, ParentElement, Render, div, px, relative, rems, uniform_list, App, AppContext, Context, Div, Entity,
SharedString, Styled, Task, Window, InteractiveElement, IntoElement, ParentElement, Render, SharedString, Styled, Task, Window,
}; };
use gpui_tokio::Tokio; use gpui_tokio::Tokio;
use i18n::{shared_t, t}; use i18n::{shared_t, t};
@@ -14,6 +18,7 @@ use smallvec::{smallvec, SmallVec};
use theme::ActiveTheme; use theme::ActiveTheme;
use ui::avatar::Avatar; use ui::avatar::Avatar;
use ui::button::{Button, ButtonRounded, ButtonVariants}; use ui::button::{Button, ButtonRounded, ButtonVariants};
use ui::indicator::Indicator;
use ui::{h_flex, v_flex, ContextModal, Icon, IconName, Sizable, StyledExt}; 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> { pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity<Screening> {
@@ -24,9 +29,10 @@ pub struct Screening {
profile: Profile, profile: Profile,
verified: bool, verified: bool,
followed: bool, followed: bool,
dm_relays: bool, dm_relays: Option<bool>,
mutual_contacts: usize, active: Option<bool>,
_tasks: SmallVec<[Task<()>; 1]>, mutual_contacts: Vec<Profile>,
_tasks: SmallVec<[Task<()>; 4]>,
} }
impl Screening { impl Screening {
@@ -37,33 +43,69 @@ impl Screening {
let mut tasks = smallvec![]; 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 client = nostr_client();
let follow = Filter::new() // Check if user is in contact list
.kind(Kind::ContactList) let contacts = client.database().contacts_public_keys(identity).await;
.author(identity) let followed = contacts.unwrap_or_default().contains(&public_key);
.pubkey(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); .limit(1);
let contacts = Filter::new() if let Ok(mut stream) = client
.kind(Kind::ContactList) .stream_events_from(BOOTSTRAP_RELAYS, filter, Duration::from_secs(2))
.pubkey(public_key) .await
.limit(1); {
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) .kind(Kind::InboxRelays)
.author(public_key) .author(public_key)
.limit(1); .limit(1);
let is_follow = client.database().count(follow).await.unwrap_or(0) >= 1; if let Ok(mut stream) = client.stream_events(filter, Duration::from_secs(2)).await {
let mutual_contacts = client.database().count(contacts).await.unwrap_or(0); while stream.next().await.is_some() {
let dm_relays = client.database().count(relays).await.unwrap_or(0) >= 1; 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 { Some(Tokio::spawn(cx, async move {
nip05_verify(public_key, &address).await.unwrap_or(false) nip05_verify(public_key, &address).await.unwrap_or(false)
})) }))
@@ -72,20 +114,49 @@ impl Screening {
}; };
tasks.push( tasks.push(
// Load all necessary data // Run the contact check in the background
cx.spawn_in(window, async move |this, cx| { 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.update(cx, |this, cx| {
this.followed = followed; this.followed = followed;
this.mutual_contacts = mutual_contacts; this.mutual_contacts = mutual_contacts;
this.dm_relays = dm_relays;
cx.notify(); cx.notify();
}) })
.ok(); .ok();
}),
);
// Update the NIP05 verification status if user has NIP05 address tasks.push(
if let Some(task) = verify_nip05 { // 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 { if let Ok(verified) = task.await {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.verified = verified; this.verified = verified;
@@ -101,8 +172,9 @@ impl Screening {
profile, profile,
verified: false, verified: false,
followed: false, followed: false,
dm_relays: false, dm_relays: None,
mutual_contacts: 0, active: None,
mutual_contacts: vec![],
_tasks: tasks, _tasks: tasks,
} }
} }
@@ -141,12 +213,54 @@ impl Screening {
}) })
.detach(); .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 { impl Render for Screening {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let proxy = AppSettings::get_proxy_user_avatars(cx); let proxy = AppSettings::get_proxy_user_avatars(cx);
let shorten_pubkey = shorten_pubkey(self.profile.public_key(), 8); let shorten_pubkey = shorten_pubkey(self.profile.public_key(), 8);
let total_mutuals = self.mutual_contacts.len();
v_flex() v_flex()
.gap_4() .gap_4()
@@ -168,12 +282,10 @@ impl Render for Screening {
h_flex() h_flex()
.gap_3() .gap_3()
.child( .child(
div() h_flex()
.p_1() .p_1()
.flex_1() .flex_1()
.h_7() .h_7()
.flex()
.items_center()
.justify_center() .justify_center()
.rounded_full() .rounded_full()
.bg(cx.theme().surface_background) .bg(cx.theme().surface_background)
@@ -217,25 +329,54 @@ impl Render for Screening {
.items_start() .items_start()
.gap_2() .gap_2()
.text_sm() .text_sm()
.child(status_badge(self.followed, cx)) .child(status_badge(Some(self.followed), cx))
.child( .child(
v_flex() v_flex()
.text_sm() .text_sm()
.child(shared_t!("screening.contact_label")) .child(shared_t!("screening.contact_label"))
.child(div().text_color(cx.theme().text_muted).child({ .child(
if self.followed { div()
shared_t!("screening.contact") .line_clamp(1)
} else { .text_color(cx.theme().text_muted)
shared_t!("screening.not_contact") .child({
} if self.followed {
})), shared_t!("screening.contact")
} else {
shared_t!("screening.not_contact")
}
}),
),
), ),
) )
.child( .child(
h_flex() h_flex()
.items_start() .items_start()
.gap_2() .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( .child(
v_flex() v_flex()
.text_sm() .text_sm()
@@ -246,35 +387,61 @@ impl Render for Screening {
shared_t!("screening.nip05_label") shared_t!("screening.nip05_label")
} }
}) })
.child(div().text_color(cx.theme().text_muted).child({ .child(
if self.address(cx).is_some() { div()
if self.verified { .line_clamp(1)
shared_t!("screening.nip05_ok") .text_color(cx.theme().text_muted)
} else { .child({
shared_t!("screening.nip05_failed") if self.address(cx).is_some() {
} if self.verified {
} else { shared_t!("screening.nip05_ok")
shared_t!("screening.nip05_empty") } else {
} shared_t!("screening.nip05_failed")
})), }
} else {
shared_t!("screening.nip05_empty")
}
}),
),
), ),
) )
.child( .child(
h_flex() h_flex()
.items_start() .items_start()
.gap_2() .gap_2()
.child(status_badge(self.mutual_contacts > 0, cx)) .child(status_badge(Some(total_mutuals > 0), cx))
.child( .child(
v_flex() v_flex()
.text_sm() .text_sm()
.child(shared_t!("screening.mutual_label")) .child(
.child(div().text_color(cx.theme().text_muted).child({ h_flex()
if self.mutual_contacts > 0 { .gap_0p5()
shared_t!("screening.mutual", u = self.mutual_contacts) .child(shared_t!("screening.mutual_label"))
} else { .child(
shared_t!("screening.no_mutual") 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( .child(
@@ -287,36 +454,43 @@ impl Render for Screening {
.w_full() .w_full()
.text_sm() .text_sm()
.child({ .child({
if self.dm_relays { if self.dm_relays == Some(true) {
shared_t!("screening.relay_found") shared_t!("screening.relay_found")
} else { } else {
shared_t!("screening.relay_empty") shared_t!("screening.relay_empty")
} }
}) })
.child(div().w_full().text_color(cx.theme().text_muted).child( .child(
{ div()
if self.dm_relays { .w_full()
shared_t!("screening.relay_found_desc") .line_clamp(1)
} else { .text_color(cx.theme().text_muted)
shared_t!("screening.relay_empty_desc") .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 { fn status_badge(status: Option<bool>, cx: &App) -> Div {
div() div().pt_1().flex_shrink_0().map(|this| {
.pt_1() if let Some(status) = status {
.flex_shrink_0() this.child(Icon::new(IconName::CheckCircleFill).small().text_color({
.child(Icon::new(IconName::CheckCircleFill).small().text_color({ if status {
if status { cx.theme().icon_accent
cx.theme().icon_accent } else {
} else { cx.theme().icon_muted
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 anyhow::{anyhow, Error};
use global::constants::NIP17_RELAYS; use global::constants::NIP17_RELAYS;
use global::nostr_client; use global::{css, nostr_client};
use gpui::prelude::FluentBuilder; use gpui::prelude::FluentBuilder;
use gpui::{ use gpui::{
div, px, uniform_list, App, AppContext, Context, Entity, InteractiveElement, IntoElement, 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::modal::ModalButtonProps;
use ui::{h_flex, v_flex, ContextModal, IconName, Sizable, StyledExt}; 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> { pub fn init(kind: Kind, window: &mut Window, cx: &mut App) -> Entity<SetupRelay> {
cx.new(|cx| SetupRelay::new(kind, window, cx)) cx.new(|cx| SetupRelay::new(kind, window, cx))
} }
@@ -220,7 +218,16 @@ impl SetupRelay {
} }
// Fetch gift wrap events // 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(()) Ok(())
}); });

View File

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

View File

@@ -219,6 +219,12 @@ screening:
en: "This person is one of your contacts." en: "This person is one of your contacts."
not_contact: not_contact:
en: "This person is not one of your contacts." 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: mutual_label:
en: "Mutual contacts" en: "Mutual contacts"
mutual: mutual: