From 031883c278f4a23b57b52c291a3b0266a135e8b6 Mon Sep 17 00:00:00 2001 From: reya Date: Sat, 7 Feb 2026 20:52:17 +0700 Subject: [PATCH] . --- Cargo.lock | 565 +++++---------------------- Cargo.toml | 2 +- crates/chat/src/lib.rs | 142 +++---- crates/chat/src/message.rs | 10 +- crates/chat/src/room.rs | 52 ++- crates/common/src/display.rs | 38 -- crates/coop/src/dialogs/screening.rs | 299 ++++++++------ crates/person/src/person.rs | 9 +- crates/state/Cargo.toml | 2 +- crates/state/src/lib.rs | 19 +- 10 files changed, 393 insertions(+), 745 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4afe4d..90d384c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,12 +85,6 @@ dependencies = [ "equator", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -152,9 +146,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "ar_archive_writer" @@ -536,15 +530,6 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "atoi" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" -dependencies = [ - "num-traits", -] - [[package]] name = "atomic" version = "0.5.3" @@ -1279,7 +1264,7 @@ dependencies = [ [[package]] name = "collections" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "indexmap", "rustc-hash 2.1.1", @@ -1364,12 +1349,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "const-random" version = "0.1.18" @@ -1590,21 +1569,22 @@ dependencies = [ [[package]] name = "cosmic-text" -version = "0.14.2" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da46a9d5a8905cc538a4a5bceb6a4510de7a51049c5588c0114efce102bcbbe8" +checksum = "8c5c9868e64aa6c5410629a83450e142c80e721c727a5bc0fb18107af6c2d66b" dependencies = [ "bitflags 2.10.0", - "fontdb 0.16.2", + "fontdb", + "harfrust", + "linebender_resource_handle", "log", "rangemap", - "rustc-hash 1.1.0", - "rustybuzz 0.14.1", + "rustc-hash 2.1.1", "self_cell", + "skrifa 0.40.0", "smol_str", "swash", "sys-locale", - "ttf-parser 0.21.1", "unicode-bidi", "unicode-linebreak", "unicode-script", @@ -1620,21 +1600,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - [[package]] name = "crc32fast" version = "1.5.0" @@ -1729,17 +1694,6 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - [[package]] name = "derive_more" version = "0.99.20" @@ -1756,7 +1710,7 @@ dependencies = [ [[package]] name = "derive_refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "proc-macro2", "quote", @@ -1788,7 +1742,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", - "const-oid", "crypto-common", "subtle", ] @@ -1893,12 +1846,6 @@ dependencies = [ "ui", ] -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - [[package]] name = "downcast-rs" version = "1.2.1" @@ -1958,9 +1905,6 @@ name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -dependencies = [ - "serde", -] [[package]] name = "embed-resource" @@ -2078,17 +2022,6 @@ dependencies = [ "svg_fmt", ] -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - [[package]] name = "euclid" version = "0.22.13" @@ -2301,6 +2234,15 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "font-types" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4d2d0cf79d38430cc9dc9aadec84774bff2e1ba30ae2bf6c16cfce9385a23" +dependencies = [ + "bytemuck", +] + [[package]] name = "fontconfig-parser" version = "0.5.8" @@ -2310,20 +2252,6 @@ dependencies = [ "roxmltree", ] -[[package]] -name = "fontdb" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" -dependencies = [ - "fontconfig-parser", - "log", - "memmap2 0.9.9", - "slotmap", - "tinyvec", - "ttf-parser 0.20.0", -] - [[package]] name = "fontdb" version = "0.23.0" @@ -2335,7 +2263,7 @@ dependencies = [ "memmap2 0.9.9", "slotmap", "tinyvec", - "ttf-parser 0.25.1", + "ttf-parser", ] [[package]] @@ -2477,17 +2405,6 @@ dependencies = [ "futures-util", ] -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - [[package]] name = "futures-io" version = "0.3.31" @@ -2711,7 +2628,7 @@ dependencies = [ [[package]] name = "gpui" version = "0.2.2" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "anyhow", "as-raw-xcb-connection", @@ -2813,7 +2730,7 @@ dependencies = [ [[package]] name = "gpui_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -2824,7 +2741,7 @@ dependencies = [ [[package]] name = "gpui_tokio" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "anyhow", "gpui", @@ -2869,6 +2786,19 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "harfrust" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9f40651a03bc0f7316bd75267ff5767e93017ef3cfffe76c6aa7252cc5a31c" +dependencies = [ + "bitflags 2.10.0", + "bytemuck", + "core_maths", + "read-fonts 0.37.0", + "smallvec", +] + [[package]] name = "hashbrown" version = "0.9.1" @@ -2890,8 +2820,6 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "allocator-api2", - "equivalent", "foldhash", ] @@ -2901,15 +2829,6 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -[[package]] -name = "hashlink" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] - [[package]] name = "heck" version = "0.4.1" @@ -3057,7 +2976,7 @@ dependencies = [ [[package]] name = "http_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "anyhow", "async-compression", @@ -3082,7 +3001,7 @@ dependencies = [ [[package]] name = "http_client_tls" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "rustls", "rustls-platform-verifier", @@ -3629,15 +3548,10 @@ dependencies = [ ] [[package]] -name = "libsqlite3-sys" -version = "0.30.1" +name = "linebender_resource_handle" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" [[package]] name = "linicon" @@ -3843,7 +3757,7 @@ dependencies = [ [[package]] name = "media" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "anyhow", "bindgen", @@ -4139,13 +4053,14 @@ dependencies = [ ] [[package]] -name = "nostr-gossip-sqlite" +name = "nostr-gossip-memory" version = "0.44.0" source = "git+https://github.com/rust-nostr/nostr#9031e3684c661690a4c61865ac11d311456371e7" dependencies = [ + "indexmap", + "lru", "nostr", "nostr-gossip", - "sqlx", "tokio", ] @@ -4691,15 +4606,6 @@ dependencies = [ "hmac", ] -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -4709,7 +4615,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perf" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "collections", "serde", @@ -4836,27 +4742,6 @@ dependencies = [ "futures-io", ] -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" version = "0.3.32" @@ -5315,7 +5200,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" dependencies = [ "bytemuck", - "font-types", + "font-types 0.10.1", +] + +[[package]] +name = "read-fonts" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b634fabf032fab15307ffd272149b622260f55974d9fad689292a5d33df02e5" +dependencies = [ + "bytemuck", + "core_maths", + "font-types 0.11.0", ] [[package]] @@ -5379,7 +5275,7 @@ dependencies = [ [[package]] name = "refineable" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "derive_refineable", ] @@ -5478,7 +5374,7 @@ dependencies = [ [[package]] name = "reqwest_client" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "anyhow", "bytes", @@ -5533,7 +5429,7 @@ dependencies = [ [[package]] name = "rope" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "arrayvec", "log", @@ -5551,26 +5447,6 @@ version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - [[package]] name = "rust-embed" version = "8.11.0" @@ -5761,23 +5637,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "rustybuzz" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" -dependencies = [ - "bitflags 2.10.0", - "bytemuck", - "libm", - "smallvec", - "ttf-parser 0.21.1", - "unicode-bidi-mirroring 0.2.0", - "unicode-ccc 0.2.0", - "unicode-properties", - "unicode-script", -] - [[package]] name = "rustybuzz" version = "0.20.1" @@ -5789,9 +5648,9 @@ dependencies = [ "core_maths", "log", "smallvec", - "ttf-parser 0.25.1", - "unicode-bidi-mirroring 0.4.0", - "unicode-ccc 0.4.0", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", "unicode-properties", "unicode-script", ] @@ -5832,7 +5691,7 @@ dependencies = [ [[package]] name = "scheduler" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "async-task", "backtrace", @@ -6179,16 +6038,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - [[package]] name = "simd-adler32" version = "0.3.8" @@ -6226,7 +6075,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c31071dedf532758ecf3fed987cdb4bd9509f900e026ab684b4ecb81ea49841" dependencies = [ "bytemuck", - "read-fonts", + "read-fonts 0.35.0", +] + +[[package]] +name = "skrifa" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdfe3d2475fbd7ddd1f3e5cf8288a30eb3e5f95832829570cd88115a7434ac" +dependencies = [ + "bytemuck", + "read-fonts 0.37.0", ] [[package]] @@ -6249,9 +6108,6 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -dependencies = [ - "serde", -] [[package]] name = "smol" @@ -6272,9 +6128,9 @@ dependencies = [ [[package]] name = "smol_str" -version = "0.2.2" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +checksum = "0f7a918bd2a9951d18ee6e48f076843e8e73a9a5d22cf05bcd4b7a81bdd04e17" [[package]] name = "socket2" @@ -6313,204 +6169,6 @@ dependencies = [ "bitflags 2.10.0", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "sqlx" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" -dependencies = [ - "base64", - "bytes", - "crc", - "crossbeam-queue", - "either", - "event-listener 5.4.1", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.15.5", - "hashlink", - "indexmap", - "log", - "memchr", - "once_cell", - "percent-encoding", - "serde", - "serde_json", - "sha2", - "smallvec", - "thiserror 2.0.18", - "tokio", - "tokio-stream", - "tracing", - "url", -] - -[[package]] -name = "sqlx-macros" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn 2.0.114", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" -dependencies = [ - "dotenvy", - "either", - "heck 0.5.0", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn 2.0.114", - "tokio", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" -dependencies = [ - "atoi", - "base64", - "bitflags 2.10.0", - "byteorder", - "bytes", - "crc", - "digest", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand 0.8.5", - "rsa", - "serde", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" -dependencies = [ - "atoi", - "base64", - "bitflags 2.10.0", - "byteorder", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand 0.8.5", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" -dependencies = [ - "atoi", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "thiserror 2.0.18", - "tracing", - "url", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -6562,7 +6220,7 @@ dependencies = [ "gpui_tokio", "log", "nostr-connect", - "nostr-gossip-sqlite", + "nostr-gossip-memory", "nostr-lmdb", "nostr-sdk", "petname", @@ -6589,17 +6247,6 @@ dependencies = [ "float-cmp", ] -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - [[package]] name = "strsim" version = "0.11.1" @@ -6658,7 +6305,7 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "sum_tree" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "arrayvec", "log", @@ -6767,7 +6414,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47846491253e976bdd07d0f9cc24b7daf24720d11309302ccbbc6e6b6e53550a" dependencies = [ - "skrifa", + "skrifa 0.37.0", "yazi", "zeno", ] @@ -7393,18 +7040,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "ttf-parser" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" - -[[package]] -name = "ttf-parser" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" - [[package]] name = "ttf-parser" version = "0.25.1" @@ -7491,24 +7126,12 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" -[[package]] -name = "unicode-bidi-mirroring" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" - [[package]] name = "unicode-bidi-mirroring" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfa6e8c60bb66d49db113e0125ee8711b7647b5579dc7f5f19c42357ed039fe" -[[package]] -name = "unicode-ccc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" - [[package]] name = "unicode-ccc" version = "0.4.0" @@ -7604,13 +7227,13 @@ dependencies = [ "base64", "data-url", "flate2", - "fontdb 0.23.0", + "fontdb", "imagesize", "kurbo", "log", "pico-args", "roxmltree", - "rustybuzz 0.20.1", + "rustybuzz", "simplecss", "siphasher", "strict-num", @@ -7643,7 +7266,7 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "util" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "anyhow", "async-fs", @@ -7681,7 +7304,7 @@ dependencies = [ [[package]] name = "util_macros" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "perf", "quote", @@ -8125,7 +7748,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -9062,18 +8685,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.8.38" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57cf3aa6855b23711ee9852dfc97dfaa51c45feaba5b645d0c777414d494a961" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.38" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a616990af1a287837c4fe6596ad77ef57948f787e46ce28e166facc0cc1cb75" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", @@ -9157,7 +8780,7 @@ dependencies = [ [[package]] name = "zlog" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "anyhow", "chrono", @@ -9174,7 +8797,7 @@ checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" [[package]] name = "ztracing" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" dependencies = [ "tracing", "tracing-subscriber", @@ -9185,7 +8808,7 @@ dependencies = [ [[package]] name = "ztracing_macro" version = "0.1.0" -source = "git+https://github.com/zed-industries/zed#49c777779a63a0f44abec3ba7bd490c8b1552667" +source = "git+https://github.com/zed-industries/zed#7cb4d75139b39efb0d1d1e90faf7480d0f1dc734" [[package]] name = "zune-core" diff --git a/Cargo.toml b/Cargo.toml index 251dbae..5d2a2da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ reqwest_client = { git = "https://github.com/zed-industries/zed" } nostr-lmdb = { git = "https://github.com/rust-nostr/nostr" } nostr-connect = { git = "https://github.com/rust-nostr/nostr" } nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = [ "nip96", "nip59", "nip49", "nip44" ] } -nostr-gossip-sqlite = { git = "https://github.com/rust-nostr/nostr" } +nostr-gossip-memory = { git = "https://github.com/rust-nostr/nostr" } # Others anyhow = "1.0.44" diff --git a/crates/chat/src/lib.rs b/crates/chat/src/lib.rs index a08e146..001e0e6 100644 --- a/crates/chat/src/lib.rs +++ b/crates/chat/src/lib.rs @@ -146,15 +146,15 @@ impl ChatRegistry { }) .ok(); } - NostrEvent::Eose => { + NostrEvent::Unwrapping(status) => { this.update(cx, |this, cx| { + this.set_loading(status, cx); this.get_rooms(cx); }) .ok(); } - NostrEvent::Unwrapping(status) => { + NostrEvent::Eose => { this.update(cx, |this, cx| { - this.set_loading(status, cx); this.get_rooms(cx); }) .ok(); @@ -310,10 +310,24 @@ impl ChatRegistry { /// Add a new room to the start of list. pub fn add_room(&mut self, room: I, cx: &mut Context) where - I: Into, + I: Into + 'static, { - self.rooms.insert(0, cx.new(|_| room.into())); - cx.notify(); + let nostr = NostrRegistry::global(cx); + let client = nostr.read(cx).client(); + + self.tasks.push(cx.spawn(async move |this, cx| { + if let Some(signer) = client.signer() { + if let Ok(public_key) = signer.get_public_key().await { + this.update(cx, |this, cx| { + this.rooms + .insert(0, cx.new(|_| room.into().organize(&public_key))); + cx.emit(ChatEvent::Ping); + cx.notify(); + }) + .ok(); + } + } + })); } /// Emit an open room event. @@ -407,23 +421,20 @@ impl ChatRegistry { pub fn get_rooms(&mut self, cx: &mut Context) { let task = self.get_rooms_from_database(cx); - self.tasks.push( - // Run and finished in the background - cx.spawn(async move |this, cx| { - match task.await { - Ok(rooms) => { - this.update(cx, move |this, cx| { - this.extend_rooms(rooms, cx); - this.sort(cx); - }) - .ok(); - } - Err(e) => { - log::error!("Failed to load rooms: {e}") - } - }; - }), - ); + self.tasks.push(cx.spawn(async move |this, cx| { + match task.await { + Ok(rooms) => { + this.update(cx, move |this, cx| { + this.extend_rooms(rooms, cx); + this.sort(cx); + }) + .ok(); + } + Err(e) => { + log::error!("Failed to load rooms: {e}") + } + }; + })); } /// Create a task to load rooms from the database @@ -434,8 +445,11 @@ impl ChatRegistry { cx.background_spawn(async move { let signer = client.signer().context("Signer not found")?; let public_key = signer.get_public_key().await?; + + // Get contacts let contacts = client.database().contacts_public_keys(public_key).await?; + // Construct authored filter let authored_filter = Filter::new() .kind(Kind::ApplicationSpecificData) .custom_tag(SingleLetterTag::lowercase(Alphabet::A), public_key); @@ -443,6 +457,7 @@ impl ChatRegistry { // Get all authored events let authored = client.database().query(authored_filter).await?; + // Construct addressed filter let addressed_filter = Filter::new() .kind(Kind::ApplicationSpecificData) .custom_tag(SingleLetterTag::lowercase(Alphabet::P), public_key); @@ -453,6 +468,7 @@ impl ChatRegistry { // Merge authored and addressed events let events = authored.merge(addressed); + // Collect results let mut rooms: HashSet = HashSet::new(); let mut grouped: HashMap> = HashMap::new(); @@ -468,24 +484,21 @@ impl ChatRegistry { for (_id, mut messages) in grouped.into_iter() { messages.sort_by_key(|m| Reverse(m.created_at)); + // Always use the latest message let Some(latest) = messages.first() else { continue; }; - let mut room = Room::from(latest); - - if rooms.iter().any(|r| r.id == room.id) { - continue; - } - - let mut public_keys = room.members(); - public_keys.retain(|pk| pk != &public_key); + // Construct the room from the latest message. + // + // Call `.organize` to ensure the current user is at the end of the list. + let mut room = Room::from(latest).organize(&public_key); // Check if the user has responded to the room let user_sent = messages.iter().any(|m| m.pubkey == public_key); // Check if public keys are from the user's contacts - let is_contact = public_keys.iter().any(|k| contacts.contains(k)); + let is_contact = room.members.iter().any(|k| contacts.contains(k)); // Set the room's kind based on status if user_sent || is_contact { @@ -499,6 +512,24 @@ impl ChatRegistry { }) } + /// Parse a nostr event into a message and push it to the belonging room + /// + /// If the room doesn't exist, it will be created. + /// Updates room ordering based on the most recent messages. + pub fn new_message(&mut self, message: NewMessage, cx: &mut Context) { + match self.rooms.iter().find(|e| e.read(cx).id == message.room) { + Some(room) => { + room.update(cx, |this, cx| { + this.push_message(message, cx); + }); + } + None => { + // Push the new room to the front of the list + self.add_room(message.rumor, cx); + } + } + } + /// Trigger a refresh of the opened chat rooms by their IDs pub fn refresh_rooms(&mut self, ids: Option>, cx: &mut Context) { if let Some(ids) = ids { @@ -512,53 +543,6 @@ impl ChatRegistry { } } - /// Parse a nostr event into a message and push it to the belonging room - /// - /// If the room doesn't exist, it will be created. - /// Updates room ordering based on the most recent messages. - pub fn new_message(&mut self, message: NewMessage, cx: &mut Context) { - let nostr = NostrRegistry::global(cx); - // Get the unique id - let id = message.rumor.uniq_id(); - // Get the author - let author = message.rumor.pubkey; - - match self.rooms.iter().find(|room| room.read(cx).id == id) { - Some(room) => { - let new_message = message.rumor.created_at > room.read(cx).created_at; - let created_at = message.rumor.created_at; - - // Update room - room.update(cx, |this, cx| { - // Update the last timestamp if the new message is newer - if new_message { - this.set_created_at(created_at, cx); - } - - // Set this room is ongoing if the new message is from current user - // if author == nostr.read(cx).identity().read(cx).public_key() { - // this.set_ongoing(cx); - // } - - // Emit the new message to the room - this.emit_message(message, cx); - }); - - // Resort all rooms in the registry by their created at (after updated) - if new_message { - self.sort(cx); - } - } - None => { - // Push the new room to the front of the list - self.add_room(&message.rumor, cx); - - // Notify the UI about the new room - cx.emit(ChatEvent::Ping); - } - } - } - /// Unwraps a gift-wrapped event and processes its contents. async fn extract_rumor( client: &Client, diff --git a/crates/chat/src/message.rs b/crates/chat/src/message.rs index c4cfef6..5ec33a7 100644 --- a/crates/chat/src/message.rs +++ b/crates/chat/src/message.rs @@ -1,17 +1,25 @@ use std::hash::Hash; +use common::EventUtils; use nostr_sdk::prelude::*; /// New message. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct NewMessage { pub gift_wrap: EventId, + pub room: u64, pub rumor: UnsignedEvent, } impl NewMessage { pub fn new(gift_wrap: EventId, rumor: UnsignedEvent) -> Self { - Self { gift_wrap, rumor } + let room = rumor.uniq_id(); + + Self { + gift_wrap, + room, + rumor, + } } } diff --git a/crates/chat/src/room.rs b/crates/chat/src/room.rs index bdea83e..638449c 100644 --- a/crates/chat/src/room.rs +++ b/crates/chat/src/room.rs @@ -11,7 +11,7 @@ use nostr_sdk::prelude::*; use person::{Person, PersonRegistry}; use state::{tracker, NostrRegistry}; -use crate::NewMessage; +use crate::{ChatRegistry, NewMessage}; const SEND_RETRY: usize = 10; @@ -99,16 +99,20 @@ pub enum RoomKind { Ongoing, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Room { /// Conversation ID pub id: u64, + /// The timestamp of the last message in the room pub created_at: Timestamp, + /// Subject of the room pub subject: Option, + /// All members of the room - pub members: Vec, + pub(super) members: Vec, + /// Kind pub kind: RoomKind, } @@ -145,11 +149,7 @@ impl From<&UnsignedEvent> for Room { fn from(val: &UnsignedEvent) -> Self { let id = val.uniq_id(); let created_at = val.created_at; - - // Get the members from the event's tags and event's pubkey let members = val.extract_public_keys(); - - // Get subject from tags let subject = val .tags .find(TagKind::Subject) @@ -165,6 +165,12 @@ impl From<&UnsignedEvent> for Room { } } +impl From for Room { + fn from(val: UnsignedEvent) -> Self { + Room::from(&val) + } +} + impl Room { /// Constructs a new room with the given receiver and tags. pub fn new(author: PublicKey, receivers: T) -> Self @@ -172,16 +178,30 @@ impl Room { T: IntoIterator, { let tags = Tags::from_list(receivers.into_iter().map(Tag::public_key).collect()); + // Construct an unsigned event for a direct message + // + // WARNING: never sign this event let mut event = EventBuilder::new(Kind::PrivateDirectMessage, "") .tags(tags) .build(author); - // Generate event ID + // Ensure that the ID is set event.ensure_id(); Room::from(&event) } + /// Organizes the members of the room by moving the target member to the end. + /// + /// Always call this function to ensure the current user is at the end of the list. + pub fn organize(mut self, target: &PublicKey) -> Self { + if let Some(index) = self.members.iter().position(|member| member == target) { + let member = self.members.remove(index); + self.members.push(member); + } + self + } + /// Sets the kind of the room and returns the modified room pub fn kind(mut self, kind: RoomKind) -> Self { self.kind = kind; @@ -275,9 +295,21 @@ impl Room { } } - /// Emits a new message signal to the current room - pub fn emit_message(&self, message: NewMessage, cx: &mut Context) { + /// Push a new message to the current room + pub fn push_message(&mut self, message: NewMessage, cx: &mut Context) { + let created_at = message.rumor.created_at; + let new_message = created_at > self.created_at; + + // Emit the incoming message event cx.emit(RoomEvent::Incoming(message)); + + if new_message { + self.set_created_at(created_at, cx); + // Sort chats after emitting a new message + ChatRegistry::global(cx).update(cx, |this, cx| { + this.sort(cx); + }); + } } /// Emits a signal to reload the current room's messages. diff --git a/crates/common/src/display.rs b/crates/common/src/display.rs index ab7c215..45a4dfd 100644 --- a/crates/common/src/display.rs +++ b/crates/common/src/display.rs @@ -12,44 +12,6 @@ const SECONDS_IN_MINUTE: i64 = 60; const MINUTES_IN_HOUR: i64 = 60; const HOURS_IN_DAY: i64 = 24; const DAYS_IN_MONTH: i64 = 30; -const IMAGE_RESIZER: &str = "https://wsrv.nl"; - -pub trait RenderedProfile { - fn avatar(&self) -> SharedString; - fn display_name(&self) -> SharedString; -} - -impl RenderedProfile for Profile { - fn avatar(&self) -> SharedString { - self.metadata() - .picture - .as_ref() - .filter(|picture| !picture.is_empty()) - .map(|picture| { - let url = format!( - "{IMAGE_RESIZER}/?url={picture}&w=100&h=100&fit=cover&mask=circle&n=-1" - ); - url.into() - }) - .unwrap_or_else(|| "brand/avatar.png".into()) - } - - fn display_name(&self) -> SharedString { - if let Some(display_name) = self.metadata().display_name.as_ref() { - if !display_name.is_empty() { - return SharedString::from(display_name); - } - } - - if let Some(name) = self.metadata().name.as_ref() { - if !name.is_empty() { - return SharedString::from(name); - } - } - - SharedString::from(shorten_pubkey(self.public_key(), 4)) - } -} pub trait RenderedTimestamp { fn to_human_time(&self) -> SharedString; diff --git a/crates/coop/src/dialogs/screening.rs b/crates/coop/src/dialogs/screening.rs index 4ca6ccf..e27609a 100644 --- a/crates/coop/src/dialogs/screening.rs +++ b/crates/coop/src/dialogs/screening.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::time::Duration; use anyhow::{Context as AnyhowContext, Error}; -use common::{shorten_pubkey, RenderedProfile, RenderedTimestamp, BOOTSTRAP_RELAYS}; +use common::{shorten_pubkey, RenderedTimestamp, BOOTSTRAP_RELAYS}; use gpui::prelude::FluentBuilder; use gpui::{ div, px, relative, rems, uniform_list, App, AppContext, Context, Div, Entity, @@ -11,7 +11,7 @@ use gpui::{ use nostr_sdk::prelude::*; use person::{Person, PersonRegistry}; use smallvec::{smallvec, SmallVec}; -use state::{NostrAddress, NostrRegistry}; +use state::{NostrAddress, NostrRegistry, TIMEOUT}; use theme::ActiveTheme; use ui::avatar::Avatar; use ui::button::{Button, ButtonVariants}; @@ -22,56 +22,117 @@ pub fn init(public_key: PublicKey, window: &mut Window, cx: &mut App) -> Entity< cx.new(|cx| Screening::new(public_key, window, cx)) } +/// Screening pub struct Screening { - profile: Person, + /// Public Key of the person being screened. + public_key: PublicKey, + + /// Whether the person's address is verified. verified: bool, + + /// Whether the person is followed by current user. followed: bool, + + /// Last time the person was active. last_active: Option, - mutual_contacts: Vec, - _tasks: SmallVec<[Task<()>; 3]>, + + /// All mutual contacts of the person being screened. + mutual_contacts: Vec, + + /// Async tasks + tasks: SmallVec<[Task<()>; 3]>, } impl Screening { pub fn new(public_key: PublicKey, window: &mut Window, cx: &mut Context) -> Self { - let http_client = cx.http_client(); - let nostr = NostrRegistry::global(cx); - let client = nostr.read(cx).client(); - - let persons = PersonRegistry::global(cx); - let profile = persons.read(cx).get(&public_key, cx); - - let mut tasks = smallvec![]; - - // Check WOT - let contact_check: Task), Error>> = cx.background_spawn({ - let client = nostr.read(cx).client(); - async move { - let signer = client.signer().context("Signer not found")?; - let signer_pubkey = signer.get_public_key().await?; - - // Check if user is in contact list - let contacts = client.database().contacts_public_keys(signer_pubkey).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 != signer_pubkey) { - if let Ok(metadata) = client.database().metadata(event.pubkey).await { - let profile = Profile::new(event.pubkey, metadata.unwrap_or_default()); - mutual_contacts.push(profile); - } - } - } - - Ok((followed, mutual_contacts)) - } + cx.defer_in(window, move |this, _window, cx| { + this.check_contact(cx); + this.check_wot(cx); + this.check_last_activity(cx); + this.verify_identifier(cx); }); - // Check the last activity - let activity_check = cx.background_spawn(async move { + Self { + public_key, + verified: false, + followed: false, + last_active: None, + mutual_contacts: vec![], + tasks: smallvec![], + } + } + + fn check_contact(&mut self, cx: &mut Context) { + let nostr = NostrRegistry::global(cx); + let client = nostr.read(cx).client(); + let public_key = self.public_key; + + let task: Task> = cx.background_spawn(async move { + let signer = client.signer().context("Signer not found")?; + let signer_pubkey = signer.get_public_key().await?; + + // Check if user is in contact list + let contacts = client.database().contacts_public_keys(signer_pubkey).await; + let followed = contacts.unwrap_or_default().contains(&public_key); + + Ok(followed) + }); + + self.tasks.push(cx.spawn(async move |this, cx| { + let result = task.await.unwrap_or(false); + + this.update(cx, |this, cx| { + this.followed = result; + cx.notify(); + }) + .ok(); + })); + } + + fn check_wot(&mut self, cx: &mut Context) { + let nostr = NostrRegistry::global(cx); + let client = nostr.read(cx).client(); + let public_key = self.public_key; + + let task: Task, Error>> = cx.background_spawn(async move { + let signer = client.signer().context("Signer not found")?; + let signer_pubkey = signer.get_public_key().await?; + + // Check mutual contacts + let filter = Filter::new().kind(Kind::ContactList).pubkey(public_key); + let mut mutual_contacts = vec![]; + + if let Ok(events) = client.database().query(filter).await { + for event in events.into_iter().filter(|ev| ev.pubkey != signer_pubkey) { + mutual_contacts.push(event.pubkey); + } + } + + Ok(mutual_contacts) + }); + + self.tasks.push(cx.spawn(async move |this, cx| { + match task.await { + Ok(contacts) => { + this.update(cx, |this, cx| { + this.mutual_contacts = contacts; + cx.notify(); + }) + .ok(); + } + Err(e) => { + log::error!("Failed to fetch mutual contacts: {}", e); + } + }; + })); + } + + fn check_last_activity(&mut self, cx: &mut Context) { + let nostr = NostrRegistry::global(cx); + let client = nostr.read(cx).client(); + let public_key = self.public_key; + + let task: Task> = cx.background_spawn(async move { let filter = Filter::new().author(public_key).limit(1); let mut activity: Option = None; @@ -83,7 +144,7 @@ impl Screening { if let Ok(mut stream) = client .stream_events(target) - .timeout(Duration::from_secs(2)) + .timeout(Duration::from_secs(TIMEOUT)) .await { while let Some((_url, event)) = stream.next().await { @@ -96,78 +157,61 @@ impl Screening { activity }); - // Verify the NIP05 address if available - let addr_check = profile.metadata().nip05.and_then(|address| { - Nip05Address::parse(&address).ok().map(|addr| { - cx.background_spawn(async move { addr.verify(&http_client, &public_key).await }) + self.tasks.push(cx.spawn(async move |this, cx| { + let result = task.await; + + this.update(cx, |this, cx| { + this.last_active = result; + cx.notify(); }) - }); - - tasks.push( - // Run the contact check in the background - cx.spawn_in(window, async move |this, cx| { - if let Ok((followed, mutual_contacts)) = contact_check.await { - this.update(cx, |this, cx| { - this.followed = followed; - this.mutual_contacts = mutual_contacts; - cx.notify(); - }) - .ok(); - } - }), - ); - - 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.last_active = active; - 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; - cx.notify(); - }) - .ok(); - } - } - }), - ); - - Self { - profile, - verified: false, - followed: false, - last_active: None, - mutual_contacts: vec![], - _tasks: tasks, - } + .ok(); + })); } - fn address(&self, _cx: &Context) -> Option { - self.profile.metadata().nip05 + fn verify_identifier(&mut self, cx: &mut Context) { + let http_client = cx.http_client(); + let public_key = self.public_key; + + // Skip if the user doesn't have a NIP-05 identifier + let Some(address) = self.address(cx) else { + return; + }; + + let task: Task> = + cx.background_spawn(async move { address.verify(&http_client, &public_key).await }); + + self.tasks.push(cx.spawn(async move |this, cx| { + let result = task.await.unwrap_or(false); + + this.update(cx, |this, cx| { + this.verified = result; + cx.notify(); + }) + .ok(); + })); } - fn open_njump(&mut self, _window: &mut Window, cx: &mut App) { - let Ok(bech32) = self.profile.public_key().to_bech32(); + fn profile(&self, cx: &Context) -> Person { + let persons = PersonRegistry::global(cx); + persons.read(cx).get(&self.public_key, cx) + } + + fn address(&self, cx: &Context) -> Option { + self.profile(cx) + .metadata() + .nip05 + .and_then(|addr| Nip05Address::parse(&addr).ok()) + } + + fn open_njump(&mut self, _window: &mut Window, cx: &mut Context) { + let Ok(bech32) = self.profile(cx).public_key().to_bech32(); cx.open_url(&format!("https://njump.me/{bech32}")); } fn report(&mut self, window: &mut Window, cx: &mut Context) { let nostr = NostrRegistry::global(cx); let client = nostr.read(cx).client(); - let public_key = self.profile.public_key(); + let public_key = self.public_key; let task: Task> = cx.background_spawn(async move { let tag = Tag::public_key_report(public_key, Report::Impersonation); @@ -180,7 +224,7 @@ impl Screening { Ok(()) }); - cx.spawn_in(window, async move |_, cx| { + self.tasks.push(cx.spawn_in(window, async move |_, cx| { if task.await.is_ok() { cx.update(|window, cx| { window.close_modal(cx); @@ -188,8 +232,7 @@ impl Screening { }) .ok(); } - }) - .detach(); + })); } fn mutual_contacts(&mut self, window: &mut Window, cx: &mut Context) { @@ -202,25 +245,27 @@ impl Screening { this.title(SharedString::from("Mutual contacts")).child( v_flex().gap_1().pb_4().child( uniform_list("contacts", total, move |range, _window, cx| { + let persons = PersonRegistry::global(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()).size(rems(1.75))) - .child(contact.display_name()), - ); - } + let Some(contact) = contacts.get(ix) else { + continue; + }; + let profile = persons.read(cx).get(contact, cx); + + 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(profile.avatar()).size(rems(1.75))) + .child(profile.name()), + ); } items @@ -234,7 +279,9 @@ impl Screening { impl Render for Screening { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { - let shorten_pubkey = shorten_pubkey(self.profile.public_key(), 8); + let profile = self.profile(cx); + let shorten_pubkey = shorten_pubkey(self.public_key, 8); + let total_mutuals = self.mutual_contacts.len(); let last_active = self.last_active.map(|_| true); @@ -246,12 +293,12 @@ impl Render for Screening { .items_center() .justify_center() .text_center() - .child(Avatar::new(self.profile.avatar()).size(rems(4.))) + .child(Avatar::new(profile.avatar()).size(rems(4.))) .child( div() .font_semibold() .line_height(relative(1.25)) - .child(self.profile.name()), + .child(profile.name()), ), ) .child( diff --git a/crates/person/src/person.rs b/crates/person/src/person.rs index a66a2eb..cd6a2a8 100644 --- a/crates/person/src/person.rs +++ b/crates/person/src/person.rs @@ -5,6 +5,8 @@ use gpui::SharedString; use nostr_sdk::prelude::*; use state::Announcement; +const IMAGE_RESIZER: &str = "https://wsrv.nl"; + /// Person #[derive(Debug, Clone)] pub struct Person { @@ -86,7 +88,12 @@ impl Person { .picture .as_ref() .filter(|picture| !picture.is_empty()) - .map(|picture| picture.into()) + .map(|picture| { + let url = format!( + "{IMAGE_RESIZER}/?url={picture}&w=100&h=100&fit=cover&mask=circle&n=-1" + ); + url.into() + }) .unwrap_or_else(|| "brand/avatar.png".into()) } diff --git a/crates/state/Cargo.toml b/crates/state/Cargo.toml index bc3b846..2d04fc2 100644 --- a/crates/state/Cargo.toml +++ b/crates/state/Cargo.toml @@ -10,7 +10,7 @@ common = { path = "../common" } nostr-sdk.workspace = true nostr-lmdb.workspace = true nostr-connect.workspace = true -nostr-gossip-sqlite.workspace = true +nostr-gossip-memory.workspace = true gpui.workspace = true gpui_tokio.workspace = true diff --git a/crates/state/src/lib.rs b/crates/state/src/lib.rs index cd8830d..8b7992f 100644 --- a/crates/state/src/lib.rs +++ b/crates/state/src/lib.rs @@ -6,9 +6,8 @@ use std::time::Duration; use anyhow::{anyhow, Context as AnyhowContext, Error}; use common::{config_dir, CLIENT_NAME}; use gpui::{App, AppContext, Context, Entity, Global, Subscription, Task}; -use gpui_tokio::Tokio; use nostr_connect::prelude::*; -use nostr_gossip_sqlite::prelude::*; +use nostr_gossip_memory::prelude::*; use nostr_lmdb::NostrLmdb; use nostr_sdk::prelude::*; @@ -125,20 +124,6 @@ impl NostrRegistry { .expect("Failed to initialize database") }); - // Use tokio to spawn a task to build the gossip instance - let build_gossip_sqlite = Tokio::spawn(cx, async move { - NostrGossipSqlite::open(config_dir().join("gossip")) - .await - .expect("Failed to initialize gossip") - }); - - // Initialize the nostr gossip instance - let gossip = cx.foreground_executor().block_on(async move { - build_gossip_sqlite - .await - .expect("Failed to initialize gossip") - }); - // Construct the nostr signer let app_keys = Self::create_or_init_app_keys().unwrap_or(Keys::generate()); let signer = Arc::new(CoopSigner::new(app_keys.clone())); @@ -150,7 +135,7 @@ impl NostrRegistry { // Construct the nostr client let client = ClientBuilder::default() .signer(signer.clone()) - .gossip(gossip) + .gossip(NostrGossipMemory::unbounded()) .database(lmdb) .automatic_authentication(false) .verify_subscriptions(false)