lib

Core libraries for Radroots
git clone https://radroots.dev/git/lib.git
Log | Files | Refs | README | LICENSE

commit ea69f478e3f6631449f3cd27f72388c7d0c62c8e
parent 932ea04384e60fb50f317d26aa100b1ea4441559
Author: triesap <tyson@radroots.org>
Date:   Fri, 22 Aug 2025 17:15:16 -0700

nostr: add `radroots-nostr` crate

Diffstat:
MCargo.lock | 783++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
MCargo.toml | 4++++
Acrates/nostr/Cargo.toml | 24++++++++++++++++++++++++
Acrates/nostr/src/client.rs | 22++++++++++++++++++++++
Acrates/nostr/src/codec_adapters.rs | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/nostr/src/error.rs | 36++++++++++++++++++++++++++++++++++++
Acrates/nostr/src/events/jobs.rs | 33+++++++++++++++++++++++++++++++++
Acrates/nostr/src/events/mod.rs | 26++++++++++++++++++++++++++
Acrates/nostr/src/filter.rs | 13+++++++++++++
Acrates/nostr/src/lib.rs | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/nostr/src/metadata.rs | 41+++++++++++++++++++++++++++++++++++++++++
Acrates/nostr/src/nip11.rs | 20++++++++++++++++++++
Acrates/nostr/src/parse.rs | 17+++++++++++++++++
Acrates/nostr/src/relays.rs | 16++++++++++++++++
Acrates/nostr/src/tags.rs | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Acrates/nostr/src/util.rs | 20++++++++++++++++++++
16 files changed, 1322 insertions(+), 6 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -28,6 +28,17 @@ dependencies = [ ] [[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] name = "ahash" version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -149,6 +160,49 @@ dependencies = [ ] [[package]] +name = "async-utility" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34a3b57207a7a1007832416c3e4862378c8451b4e8e093e436f48c2d3d2c151" +dependencies = [ + "futures-util", + "gloo-timers", + "tokio", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-wsocket" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7d8c7d34a225ba919dd9ba44d4b9106d20142da545e086be8ae21d1897e043" +dependencies = [ + "async-utility", + "futures", + "futures-util", + "js-sys", + "tokio", + "tokio-rustls", + "tokio-socks", + "tokio-tungstenite", + "url", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "atomic-destructor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef49f5882e4b6afaac09ad239a4f8c70a24b8f2b0897edb1f706008efd109cf4" + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -271,6 +325,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] name = "cbc" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -295,6 +355,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] name = "chacha20" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -480,11 +546,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "typenum", ] [[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] name = "deranged" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -525,6 +597,12 @@ dependencies = [ ] [[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -556,6 +634,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] name = "form_urlencoded" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -565,6 +649,71 @@ dependencies = [ ] [[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -594,9 +743,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] @@ -606,6 +757,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -661,6 +824,108 @@ dependencies = [ ] [[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots 1.0.2", +] + +[[package]] +name = "hyper-util" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.0", + "tokio", + "tower-service", + "tracing", +] + +[[package]] name = "iana-time-zone" version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -835,6 +1100,22 @@ dependencies = [ ] [[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -898,6 +1179,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] +name = "lru" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86ea4e65087ff52f3862caff188d489f1fab49a0cb09e01b2e3f1a617b10aaed" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -939,6 +1232,12 @@ dependencies = [ ] [[package]] +name = "negentropy" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d" + +[[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -954,6 +1253,7 @@ version = "0.43.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62a97d745f1bd8d5e05a978632bbb87b0614567d5142906fe7c86fb2440faac6" dependencies = [ + "aes", "base64 0.22.1", "bech32", "bip39", @@ -972,6 +1272,47 @@ dependencies = [ ] [[package]] +name = "nostr-database" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c75a8c2175d2785ba73cfddef21d1e30da5fbbdf158569b6808ba44973a15b" +dependencies = [ + "lru", + "nostr", + "tokio", +] + +[[package]] +name = "nostr-relay-pool" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "265d9b44771ed15db93b183a0c93dbb703b2b0d0b74dffb5c2a081be52373a5a" +dependencies = [ + "async-utility", + "async-wsocket", + "atomic-destructor", + "lru", + "negentropy", + "nostr", + "nostr-database", + "tokio", + "tracing", +] + +[[package]] +name = "nostr-sdk" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "599f8963d6a1522a13b1a2b0ea6e168acfc367706606f1d33fa595e91fa22db0" +dependencies = [ + "async-utility", + "nostr", + "nostr-database", + "nostr-relay-pool", + "tokio", +] + +[[package]] name = "nu-ansi-term" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1046,7 +1387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", - "rand_core", + "rand_core 0.6.4", "subtle", ] @@ -1123,6 +1464,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] name = "poly1305" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1167,6 +1514,61 @@ dependencies = [ ] [[package]] +name = "quinn" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.16", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.16", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.5.10", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1221,6 +1623,20 @@ dependencies = [ ] [[package]] +name = "radroots-nostr" +version = "0.1.0" +dependencies = [ + "nostr", + "nostr-sdk", + "radroots-events", + "radroots-events-codec", + "reqwest", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] name = "radroots-runtime" version = "0.1.0" dependencies = [ @@ -1256,8 +1672,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -1267,7 +1693,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -1280,6 +1716,15 @@ dependencies = [ ] [[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1324,6 +1769,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] +name = "reqwest" +version = "0.12.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots 1.0.2", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] name = "ron" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1373,6 +1870,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] name = "rustix" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1386,6 +1889,41 @@ dependencies = [ ] [[package]] +name = "rustls" +version = "0.23.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1424,7 +1962,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "rand", + "rand 0.8.5", "secp256k1-sys", "serde", ] @@ -1480,6 +2018,29 @@ dependencies = [ ] [[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1527,6 +2088,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1556,6 +2137,15 @@ dependencies = [ ] [[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1700,12 +2290,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", + "bytes", "io-uring", "libc", "mio", "pin-project-lite", "signal-hook-registry", "slab", + "socket2 0.6.0", "tokio-macros", "windows-sys 0.59.0", ] @@ -1722,6 +2314,44 @@ dependencies = [ ] [[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-socks" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4770b8024672c1101b3f6733eab95b18007dbe0847a8afe341fcf79e06043f" +dependencies = [ + "either", + "futures-util", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084" +dependencies = [ + "futures-util", + "log", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.26.11", +] + +[[package]] name = "toml" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1763,6 +2393,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" [[package]] +name = "tower" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1836,6 +2511,31 @@ dependencies = [ ] [[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "rand 0.9.2", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.16", + "utf-8", +] + +[[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1901,6 +2601,12 @@ dependencies = [ ] [[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] name = "url" version = "2.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1913,6 +2619,12 @@ dependencies = [ ] [[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1937,6 +2649,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1978,6 +2699,19 @@ dependencies = [ ] [[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2020,6 +2754,34 @@ dependencies = [ ] [[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.2", +] + +[[package]] +name = "webpki-roots" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +dependencies = [ + "rustls-pki-types", +] + +[[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2102,6 +2864,15 @@ dependencies = [ [[package]] name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" diff --git a/Cargo.toml b/Cargo.toml @@ -14,14 +14,18 @@ license = "AGPL-3.0" radroots-core = { path = "crates/core", version = "0.1.0", default-features = false } radroots-events = { path = "crates/events", version = "0.1.0", default-features = false } radroots-events-codec = { path = "crates/events-codec", version = "0.1.0", default-features = false } +radroots-events-indexed = { path = "crates/events-indexed", version = "0.1.0", default-features = false } +radroots-nostr = { path = "crates/nostr", version = "0.1.0", default-features = false } radroots-trade = { path = "crates/trade", version = "0.1.0", default-features = false } anyhow = { version = "1" } clap = { version = "4" } config = { version = "0.14" } +nostr-sdk = { version = "0.43.0" } nostr = { version = "0.43.0" } serde = { version = "1", default-features = false } serde_json = { version = "1", default-features = false } +reqwest = { version = "0.12", default-features = false } rust_decimal = { version = "1", default-features = false } rust_decimal_macros = { version = "1" } tempfile = { version = "3" } diff --git a/crates/nostr/Cargo.toml b/crates/nostr/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "radroots-nostr" +version.workspace = true +edition.workspace = true +authors = ["Radroots Authors"] +rust-version.workspace = true +license.workspace = true + +[features] +default = ["std", "sdk"] +std = [] +sdk = ["dep:nostr-sdk"] +codec = ["dep:radroots-events", "dep:radroots-events-codec"] +http = ["dep:reqwest"] + +[dependencies] +radroots-events = { workspace = true, optional = true, default-features = false } +radroots-events-codec = { workspace = true, optional = true, default-features = false } +nostr = { workspace = true, features = ["nip04"] } +nostr-sdk = { workspace = true, optional = true } +reqwest = { workspace = true, optional = true, default-features = false, features = ["json", "rustls-tls"] } +serde = { workspace = true } +serde_json = { workspace = true } +thiserror = { workspace = true } diff --git a/crates/nostr/src/client.rs b/crates/nostr/src/client.rs @@ -0,0 +1,22 @@ +use core::time::Duration; +use nostr::{event::Event, event::EventBuilder, event::EventId, filter::Filter}; +use nostr_sdk::{Client, prelude::*}; + +use crate::error::NostrUtilsError; + +pub async fn nostr_send_event( + client: &Client, + event: EventBuilder, +) -> Result<Output<EventId>, NostrUtilsError> { + Ok(client.send_event_builder(event).await?) +} + +pub async fn nostr_fetch_event_by_id(client: Client, id: &str) -> Result<Event, NostrUtilsError> { + let event_id = EventId::parse(id)?; + let filter = Filter::new().id(event_id); + let events = client.fetch_events(filter, Duration::from_secs(10)).await?; + let event = events + .first() + .ok_or_else(|| NostrUtilsError::EventNotFound(event_id.to_hex()))?; + Ok(event.clone()) +} diff --git a/crates/nostr/src/codec_adapters.rs b/crates/nostr/src/codec_adapters.rs @@ -0,0 +1,118 @@ +extern crate alloc; +use alloc::{string::String, vec::Vec}; + +use nostr::event::Event; + +use radroots_events_codec::job::{ + error::JobParseError, feedback::decode as fb_decode, request::decode as req_decode, + result::decode as res_decode, +}; + +fn event_id(e: &Event) -> String { + e.id.to_hex() +} + +fn author(e: &Event) -> String { + e.pubkey.to_hex() +} + +fn published_at(e: &Event) -> u32 { + e.created_at.as_u64() as u32 +} + +fn kind_u32(e: &Event) -> u32 { + e.kind.as_u16() as u32 +} + +fn content(e: &Event) -> String { + e.content.clone() +} + +fn tags_vec(e: &Event) -> Vec<Vec<String>> { + e.tags.iter().map(|t| t.as_slice().to_vec()).collect() +} + +fn sig_hex(e: &Event) -> String { + e.sig.to_string() +} + +pub fn to_job_request_metadata( + e: &Event, +) -> Result<radroots_events::job::request::models::RadrootsJobRequestEventMetadata, JobParseError> { + req_decode::metadata_from_event( + event_id(e), + author(e), + published_at(e), + kind_u32(e), + tags_vec(e), + ) +} + +pub fn to_job_result_metadata( + e: &Event, +) -> Result<radroots_events::job::result::models::RadrootsJobResultEventMetadata, JobParseError> { + res_decode::metadata_from_event( + event_id(e), + author(e), + published_at(e), + kind_u32(e), + content(e), + tags_vec(e), + ) +} + +pub fn to_job_feedback_metadata( + e: &Event, +) -> Result<radroots_events::job::feedback::models::RadrootsJobFeedbackEventMetadata, JobParseError> +{ + fb_decode::metadata_from_event( + event_id(e), + author(e), + published_at(e), + kind_u32(e), + content(e), + tags_vec(e), + ) +} + +pub fn to_job_request_index( + e: &Event, +) -> Result<radroots_events::job::request::models::RadrootsJobRequestEventIndex, JobParseError> { + req_decode::index_from_event( + event_id(e), + author(e), + published_at(e), + kind_u32(e), + content(e), + tags_vec(e), + sig_hex(e), + ) +} + +pub fn to_job_result_index( + e: &Event, +) -> Result<radroots_events::job::result::models::RadrootsJobResultEventIndex, JobParseError> { + res_decode::index_from_event( + event_id(e), + author(e), + published_at(e), + kind_u32(e), + content(e), + tags_vec(e), + sig_hex(e), + ) +} + +pub fn to_job_feedback_index( + e: &Event, +) -> Result<radroots_events::job::feedback::models::RadrootsJobFeedbackEventIndex, JobParseError> { + fb_decode::index_from_event( + event_id(e), + author(e), + published_at(e), + kind_u32(e), + content(e), + tags_vec(e), + sig_hex(e), + ) +} diff --git a/crates/nostr/src/error.rs b/crates/nostr/src/error.rs @@ -0,0 +1,36 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum NostrUtilsError { + #[cfg(feature = "sdk")] + #[error("Client error: {0}")] + ClientError(#[from] nostr_sdk::client::Error), + + #[cfg(feature = "sdk")] + #[error("Database error: {0}")] + DatabaseError(#[from] nostr_sdk::prelude::DatabaseError), + + #[error("Event error: {0}")] + EventError(#[from] nostr::event::Error), + + #[error("Event not found: {0}")] + EventNotFound(String), + + #[error("Event builder failure: {0}")] + EventBuildError(#[from] nostr::event::builder::Error), +} + +#[derive(Debug, Error)] +pub enum NostrTagsResolveError { + #[error("Missing public key 'p' tag in encrypted event: {0:?}")] + MissingPTag(nostr::Event), + + #[error("Encrypted event recipient mismatch")] + NotRecipient, + + #[error("Decryption error: {0}")] + DecryptionError(String), + + #[error("Failed to parse decrypted tag JSON: {0}")] + ParseError(#[from] serde_json::Error), +} diff --git a/crates/nostr/src/events/jobs.rs b/crates/nostr/src/events/jobs.rs @@ -0,0 +1,33 @@ +use nostr::{ + event::{Event, EventBuilder, Tag}, + nips::nip90::{DataVendingMachineStatus, JobFeedbackData}, +}; + +use crate::error::NostrUtilsError; + +pub fn nostr_build_event_job_result( + job_request: &Event, + payload: impl Into<String>, + millisats: u64, + bolt11: Option<String>, + tags: Option<Vec<Tag>>, +) -> Result<EventBuilder, NostrUtilsError> { + let builder = EventBuilder::job_result(job_request.clone(), payload, millisats, bolt11)? + .tags(tags.unwrap_or_default()); + Ok(builder) +} + +pub fn nostr_build_event_job_feedback( + job_request: &Event, + status: &str, + extra_info: Option<String>, + tags: Option<Vec<Tag>>, +) -> Result<EventBuilder, NostrUtilsError> { + let status = status + .parse::<DataVendingMachineStatus>() + .unwrap_or(DataVendingMachineStatus::Error); + let feedback_data = JobFeedbackData::new(&job_request.clone(), status) + .extra_info(extra_info.unwrap_or_default()); + let builder = EventBuilder::job_feedback(feedback_data).tags(tags.unwrap_or_default()); + Ok(builder) +} diff --git a/crates/nostr/src/events/mod.rs b/crates/nostr/src/events/mod.rs @@ -0,0 +1,26 @@ +pub mod jobs; + +extern crate alloc; +use alloc::{string::String, vec::Vec}; + +use nostr::event::{EventBuilder, Kind, Tag, TagKind}; + +use crate::error::NostrUtilsError; + +pub fn nostr_build_events( + kind_u32: u32, + content: impl Into<String>, + tag_slices: Vec<Vec<String>>, +) -> Result<EventBuilder, NostrUtilsError> { + let mut tags: Vec<Tag> = Vec::new(); + for mut s in tag_slices { + if s.is_empty() { + continue; + } + let key = s.remove(0); + let values = s; + tags.push(Tag::custom(TagKind::Custom(key.into()), values)); + } + let builder = EventBuilder::new(Kind::Custom(kind_u32 as u16), content.into()).tags(tags); + Ok(builder) +} diff --git a/crates/nostr/src/filter.rs b/crates/nostr/src/filter.rs @@ -0,0 +1,13 @@ +use nostr::{event::Kind, filter::Filter, types::Timestamp}; + +pub fn nostr_kind(kind: u16) -> Kind { + Kind::Custom(kind) +} + +pub fn nostr_filter_kind(kind: u16) -> Filter { + Filter::new().kind(Kind::Custom(kind)) +} + +pub fn nostr_filter_new_events(filter: Filter) -> Filter { + filter.since(Timestamp::now()) +} diff --git a/crates/nostr/src/lib.rs b/crates/nostr/src/lib.rs @@ -0,0 +1,49 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +#[cfg(feature = "sdk")] +pub mod client; + +pub mod error; +pub mod events; +pub mod filter; +pub mod metadata; +pub mod parse; +pub mod relays; +pub mod tags; +pub mod util; + +#[cfg(feature = "codec")] +pub mod codec_adapters; + +#[cfg(feature = "http")] +pub mod nip11; + +pub mod prelude { + pub use crate::events::nostr_build_events; + + #[cfg(feature = "sdk")] + pub use crate::client::{nostr_fetch_event_by_id, nostr_send_event}; + + pub use crate::error::{NostrTagsResolveError, NostrUtilsError}; + + pub use crate::filter::{nostr_filter_kind, nostr_filter_new_events, nostr_kind}; + + pub use crate::events::jobs::{nostr_build_event_job_feedback, nostr_build_event_job_result}; + + pub use crate::relays::{add_relay, connect, remove_relay}; + + pub use crate::metadata::{ + build_metadata_event, fetch_latest_metadata_for_author, set_metadata, + }; + + pub use crate::parse::{parse_pubkey, parse_pubkeys}; + + pub use crate::tags::*; + + pub use crate::util::npub_string; + + #[cfg(feature = "http")] + pub use crate::nip11::fetch_nip11; +} diff --git a/crates/nostr/src/metadata.rs b/crates/nostr/src/metadata.rs @@ -0,0 +1,41 @@ +use crate::error::NostrUtilsError; +use core::time::Duration; +use nostr::{ + Kind, Metadata, event::Event, event::EventBuilder, event::EventId, filter::Filter, + key::PublicKey, +}; +use nostr_sdk::{Client, prelude::Output}; + +pub fn build_metadata_event(md: &Metadata) -> EventBuilder { + EventBuilder::metadata(md) +} + +pub async fn set_metadata( + client: &Client, + md: &Metadata, +) -> Result<Output<EventId>, NostrUtilsError> { + let builder = build_metadata_event(md); + Ok(client.send_event_builder(builder).await?) +} + +pub async fn fetch_latest_metadata_for_author( + client: &Client, + author: PublicKey, + timeout: Duration, +) -> Result<Option<Event>, NostrUtilsError> { + let filter = Filter::new().authors(vec![author]).kind(Kind::Metadata); + let stored = client.database().query(filter.clone()).await?; + let fetched = client.fetch_events(filter, timeout).await?; + + let mut latest: Option<Event> = None; + for ev in stored.into_iter().chain(fetched.into_iter()) { + if ev.kind != Kind::Metadata { + continue; + } + match &latest { + Some(cur) if ev.created_at <= cur.created_at => {} + _ => latest = Some(ev), + } + } + Ok(latest) +} diff --git a/crates/nostr/src/nip11.rs b/crates/nostr/src/nip11.rs @@ -0,0 +1,20 @@ +#[cfg(all(feature = "http", feature = "codec"))] +use radroots_events::relay_document::models::RadrootsRelayDocument; + +#[cfg(all(feature = "http", feature = "codec"))] +use crate::util::ws_to_http; + +#[cfg(all(feature = "http", feature = "codec"))] +pub async fn fetch_nip11(ws_url: &str) -> Option<RadrootsRelayDocument> { + let http_url = ws_to_http(ws_url)?; + let client = reqwest::Client::new(); + client + .get(&http_url) + .header("Accept", "application/nostr+json") + .send() + .await + .ok()? + .json::<RadrootsRelayDocument>() + .await + .ok() +} diff --git a/crates/nostr/src/parse.rs b/crates/nostr/src/parse.rs @@ -0,0 +1,17 @@ +use nostr::{key::PublicKey, nips::nip19::FromBech32}; + +#[derive(Debug, thiserror::Error)] +pub enum ParseError { + #[error("invalid pubkey format: {0}")] + Invalid(String), +} + +pub fn parse_pubkey(s: &str) -> Result<PublicKey, ParseError> { + PublicKey::from_bech32(s) + .or_else(|_| PublicKey::from_hex(s)) + .map_err(|_| ParseError::Invalid(s.to_string())) +} + +pub fn parse_pubkeys(input: &[String]) -> Result<Vec<PublicKey>, ParseError> { + input.iter().map(|s| parse_pubkey(s)).collect() +} diff --git a/crates/nostr/src/relays.rs b/crates/nostr/src/relays.rs @@ -0,0 +1,16 @@ +use crate::error::NostrUtilsError; +use nostr_sdk::Client; + +pub async fn add_relay(client: &Client, url: &str) -> Result<(), NostrUtilsError> { + client.add_relay(url).await?; + Ok(()) +} + +pub async fn remove_relay(client: &Client, url: &str) -> Result<(), NostrUtilsError> { + client.force_remove_relay(url).await?; + Ok(()) +} + +pub async fn connect(client: &Client) { + client.connect().await; +} diff --git a/crates/nostr/src/tags.rs b/crates/nostr/src/tags.rs @@ -0,0 +1,106 @@ +extern crate alloc; +use alloc::{borrow::Cow, string::String, vec::Vec}; + +use nostr::{ + event::{Event, Tag, TagKind, TagStandard}, + key::{Keys, PublicKey}, + nips::nip04, + types::RelayUrl, +}; + +use crate::error::NostrTagsResolveError; + +pub fn nostr_tag_first_value(tag: &Tag, key: &str) -> Option<String> { + if tag.kind() == TagKind::custom(key) { + tag.content().map(|v| v.to_string()) + } else { + None + } +} + +pub fn nostr_tag_at_value(tag: &Tag, index: usize) -> Option<String> { + tag.as_slice().get(index).cloned() +} + +pub fn nostr_tag_slice(tag: &Tag, start: usize) -> Option<Vec<String>> { + tag.as_slice().get(start..).map(|s| s.to_vec()) +} + +pub fn nostr_tag_relays_parse(tag: &Tag) -> Option<&Vec<RelayUrl>> { + match tag.as_standardized()? { + TagStandard::Relays(urls) => Some(urls), + _ => None, + } +} + +pub fn nostr_tags_match<'a>(tag: &'a Tag) -> Option<(&'a str, &'a [String])> { + if let TagKind::Custom(Cow::Borrowed(key)) = tag.kind() { + Some((key, &tag.as_slice()[1..])) + } else { + None + } +} + +pub fn nostr_tag_match_l(tag: &Tag) -> Option<(&str, f64)> { + let values = tag.as_slice(); + if values.len() >= 3 && values[0].eq_ignore_ascii_case("l") { + if let Ok(value) = values[1].parse::<f64>() { + return Some((values[2].as_str(), value)); + } + } + None +} + +pub fn nostr_tag_match_location(tag: &Tag) -> Option<(&str, &str, &str)> { + let values = tag.as_slice(); + if values.len() >= 4 && values[0] == "location" { + Some((values[1].as_str(), values[2].as_str(), values[3].as_str())) + } else { + None + } +} + +pub fn nostr_tag_match_geohash(tag: &Tag) -> Option<String> { + match tag.as_standardized()? { + TagStandard::Geohash(geohash) => Some(geohash.clone()), + _ => None, + } +} + +pub fn nostr_tag_match_title(tag: &Tag) -> Option<String> { + match tag.as_standardized()? { + TagStandard::Title(title) => Some(title.clone()), + _ => None, + } +} + +pub fn nostr_tag_match_summary(tag: &Tag) -> Option<String> { + match tag.as_standardized()? { + TagStandard::Summary(summary) => Some(summary.clone()), + _ => None, + } +} + +pub fn nostr_tags_resolve(event: &Event, keys: &Keys) -> Result<Vec<Tag>, NostrTagsResolveError> { + if !event.tags.iter().any(|t| t.kind() == TagKind::Encrypted) { + return Ok(event.clone().tags.to_vec()); + } + let recipient = event + .tags + .iter() + .find_map(|tag| { + if tag.kind() == TagKind::p() { + tag.content()?.parse::<PublicKey>().ok() + } else { + None + } + }) + .ok_or_else(|| NostrTagsResolveError::MissingPTag(event.clone()))?; + if recipient != keys.public_key() { + return Err(NostrTagsResolveError::NotRecipient); + } + let cleartext = nip04::decrypt(keys.secret_key(), &event.pubkey, &event.content) + .map_err(|e| NostrTagsResolveError::DecryptionError(e.to_string()))?; + let decrypted_tags: nostr::event::tag::list::Tags = serde_json::from_str(&cleartext)?; + Ok(decrypted_tags.to_vec()) +} diff --git a/crates/nostr/src/util.rs b/crates/nostr/src/util.rs @@ -0,0 +1,20 @@ +use nostr::{key::PublicKey, nips::nip19::ToBech32}; + +pub fn npub_string(pk: &PublicKey) -> Option<String> { + pk.to_bech32().ok() +} + +#[cfg(feature = "http")] +pub fn ws_to_http(ws: &str) -> Option<String> { + let mut u = reqwest::Url::parse(ws).ok()?; + let scheme = u.scheme().to_owned(); + + let new_scheme = match scheme.as_str() { + "wss" => "https", + "ws" => "http", + other => other, + }; + + u.set_scheme(new_scheme).ok()?; + Some(u.into()) +}