diff --git a/.gitignore b/.gitignore index 6b3d107..d635a5c 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,7 @@ README.md.* tmp/* +local +local/* + + diff --git a/Cargo.lock b/Cargo.lock index 522e403..b104335 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "7724808837b77f4b4de9d283820f9d98bcf496d5692934b857a2399d31ff22e6" [[package]] name = "atty" @@ -134,9 +134,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.77" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cfg-if" @@ -225,6 +225,16 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.3" @@ -261,9 +271,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf07d07d6531bfcdbe9b8b739b104610c6508dcc4d63b410585faf338241daf" +checksum = "27874566aca772cb515af4c6e997b5fe2119820bca447689145e39bb734d19a0" dependencies = [ "cc", "cxxbridge-flags", @@ -273,9 +283,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2eb5b96ecdc99f72657332953d4d9c50135af1bac34277801cc3937906ebd39" +checksum = "e7bb951f2523a49533003656a72121306b225ec16a49a09dc6b0ba0d6f3ec3c0" dependencies = [ "cc", "codespan-reporting", @@ -288,15 +298,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac040a39517fd1674e0f32177648334b0f4074625b5588a64519804ba0553b12" +checksum = "be778b6327031c1c7b61dd2e48124eee5361e6aa76b8de93692f011b08870ab4" [[package]] name = "cxxbridge-macro" -version = "1.0.83" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1362b0ddcfc4eb0a1f57b68bd77dd99f0e826958a96abd0ae9bd092e114ffed6" +checksum = "7b8a2b87662fe5a0a0b38507756ab66aff32638876a0866e5a5fc82ceb07ee49" dependencies = [ "proc-macro2", "quote", @@ -369,6 +379,15 @@ dependencies = [ "libc", ] +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + [[package]] name = "flate2" version = "1.0.25" @@ -385,6 +404,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -596,6 +630,19 @@ dependencies = [ "tokio-rustls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -650,6 +697,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "io-lifetimes" version = "1.0.3" @@ -657,7 +713,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -675,14 +731,14 @@ dependencies = [ "hermit-abi 0.2.6", "io-lifetimes", "rustix", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "js-sys" @@ -713,9 +769,9 @@ checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "link-cplusplus" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] @@ -775,14 +831,32 @@ dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.42.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] name = "nostr" -version = "0.5.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577e0cf398021cc8bdb9d4824a162d09e46aea53548c98595e424f72bee04435" +checksum = "bcb03893957a77c629de6b025b9ae34efe61e3cf93dcb2924970c2432ec16a30" dependencies = [ "aes", "anyhow", @@ -798,22 +872,22 @@ dependencies = [ "serde", "serde_json", "serde_repr", - "thiserror", "url", - "uuid", ] [[package]] name = "nostr-commander" -version = "0.0.9" +version = "0.0.10" dependencies = [ "anyhow", "atty", + "bitcoin_hashes", "chrono", "clap", "directories", "json", "nostr-sdk", + "reqwest", "serde", "serde_json", "thiserror", @@ -826,9 +900,9 @@ dependencies = [ [[package]] name = "nostr-sdk" -version = "0.5.1" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1b5eef3009481a658d0b31c81ef7eeaa176804fd640924ab714ead7f68bdad5" +checksum = "b7dd2915f7bcaa4c256246656e3c43ee26cf9fe8a4248cf1b43bb30305d18c44" dependencies = [ "anyhow", "bitcoin_hashes", @@ -887,6 +961,51 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +[[package]] +name = "openssl" +version = "0.10.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29d971fd5722fec23977260f6e81aa67d2f22cadbdc2aa049f1022d9a3be1566" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5454462c0eced1e97f2ec09036abc8da362e66802f66fd20f86854d9d8cbcbc4" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "os_str_bytes" version = "6.4.1" @@ -919,7 +1038,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -940,6 +1059,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -972,18 +1097,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.47" +version = "1.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "e9d89e5dba24725ae5678020bf8f1357a9aa7ff10736b551adbcd3f8d17d766f" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "556d0f47a940e895261e77dc200d5eadfc6ef644c179c6f5edfc105e3a2292c8" dependencies = [ "proc-macro2", ] @@ -1055,6 +1180,15 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "reqwest" version = "0.11.13" @@ -1071,10 +1205,12 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -1084,6 +1220,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-socks", "tower-service", @@ -1121,7 +1258,7 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1147,9 +1284,19 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "schannel" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +dependencies = [ + "lazy_static", + "windows-sys 0.36.1", +] [[package]] name = "scopeguard" @@ -1159,9 +1306,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" [[package]] name = "sct" @@ -1194,26 +1341,49 @@ dependencies = [ "cc", ] +[[package]] +name = "security-framework" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +checksum = "3bfa246f936730408c0abee392cc1a50b118ece708c7f630516defd64480c7d8" [[package]] name = "serde" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" +checksum = "97fed41fc1a24994d044e6db6935e69511a1153b52c15eb42493b26fa87feba0" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42a3df25b0713732468deadad63ab9da1f1fd75a48a15024b50363f128db627e" +checksum = "255abe9a125a985c05190d687b320c12f9b1f0b99445e608c21ba0782c719ad8" dependencies = [ "proc-macro2", "quote", @@ -1222,9 +1392,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.89" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +checksum = "8778cc0b528968fe72abec38b5db5a20a70d148116cd9325d2bc5f5180ca3faf" dependencies = [ "itoa", "ryu", @@ -1233,9 +1403,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" dependencies = [ "proc-macro2", "quote", @@ -1322,15 +1492,29 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" +checksum = "09ee3a69cd2c7e06684677e5629b3878b253af05e4714964204279c6bc02cf0b" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.3" @@ -1347,23 +1531,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb20089a8ba2b69debd491f8d2d023761cbf196e999218c591fa1e7e15a21907" dependencies = [ "rustix", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -1422,7 +1606,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1436,6 +1620,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -1603,9 +1797,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" @@ -1686,7 +1880,6 @@ checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" dependencies = [ "getrandom", "rand", - "serde", "uuid-macro-internal", ] @@ -1707,6 +1900,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" @@ -1861,6 +2060,19 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", +] + [[package]] name = "windows-sys" version = "0.42.0" @@ -1868,12 +2080,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_x86_64_msvc 0.42.0", ] [[package]] @@ -1882,24 +2094,48 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + [[package]] name = "windows_x86_64_gnu" version = "0.42.0" @@ -1912,6 +2148,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + [[package]] name = "windows_x86_64_msvc" version = "0.42.0" diff --git a/Cargo.toml b/Cargo.toml index 0a12cb9..a6f6b65 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ [package] name = "nostr-commander" -version = "0.0.9" +version = "0.0.10" edition = "2021" description = "simple but convenient CLI-based Nostr client app for publishing,sending and subscribing" documentation = "https://docs.rs/nostr-commander" @@ -19,7 +19,7 @@ publish = true [dependencies] anyhow = "1" -nostr-sdk = "0.5" +nostr-sdk = "0.7" tokio = { version = "1.23", features = ["full"] } clap = { version = "4.0", features = ["derive", "color", "wrap_help", "unicode"] } thiserror = "1.0" @@ -33,6 +33,8 @@ update-informer = "0.5" chrono = "0.4" json = "0.12" atty = "0.2" +bitcoin_hashes = "0.11" +reqwest = "0.11" [dev-dependencies] diff --git a/README.md b/README.md index 7a67e0f..0993f19 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ nostr-command(lin)er. A word play. - https://github.com/8go/nostr-commander-rs - https://crates.io/crates/nostr-commander - https://docs.rs/crate/nostr-commander +- https://github.com/yukibtc/nostr-rs-sdk: `nostr-sdk` used to build `nostr-commander` # Installation @@ -146,6 +147,16 @@ Options: a "check crates.io" upon request. Your privacy is protected. New release is neither downloaded, nor installed. It just informs you [possible values: check] + --usage + Prints very short help summary. Details:: See also --help, --manual + and --readme + -h, --help + Prints short help. Details:: See also --usage, --manual and --readme + --manual + Prints long help. Details:: See also --usage, --help and --readme + --readme + Prints README.md file, the documenation in Markdown. Details:: See + also --usage, --help and --manual -d, --debug... Overwrite the default log level. If not used, then the default log level set with environment variable 'RUST_LOG' will be used. If used, @@ -252,9 +263,17 @@ Options: its public key, a string in the form of 'npub1...', a Hex key, or an alias from one of your contacts. The first argument is the recipient, all further arguments are texts to be sent. E.g. '-dm - 'npub1SomeStrangeNumbers "First msg" "Second msg"' or 'dm joe "How + "npub1SomeStrangeNumbers" "First msg" "Second msg"' or '--dm joe "How about pizza tonight?"'. See also '--publish' to see how shortcut characters '-' (pipe) and '_' (streamed pipe) are handled + --send-channel-message [...] + Send one or multiple messages to one given channel. The single + destination channel is specified via its hash. See here for a channel + list: https://damus.io/channels/. The first argument is the channel + hash, all further arguments are texts to be sent. E.g. + '-send_channel_message "SomeChannelHash" "First msg" "Second msg"'. + See also '--publish' to see how shortcut characters '-' (pipe) and + '_' (streamed pipe) are handled --add-relay [...] Add one or multiple relays. A relay is specified via a URI that looks like 'wss://some.relay.org'. You can find relays by looking at @@ -331,14 +350,41 @@ Options: --relay [...] Provide one or multiple relays for argument --add-contact. They have the form 'wss://some.relay.org' - --subscribe-author [...] - Subscribe to one or more authors. Specify each author by its public - key in form of 'npub1SomePublicKey'. Alternatively you can use the - Hex form of the private key + --npub-to-hex [...] + Convert one or multiple public keys in Bech32 format ('npub1...') + into the corresponding 'hex' format. Details:: See also --hex-to-npub + --hex-to-npub [...] + Convert one or multiple public keys in 'hex' format into the + corresponding Bech32 ('npub1...') format. Details:: See also + --npub-to-hex + --get-pubkey-entity [...] + Get the entity of one or multiple public keys. Details:: This will + show you for every public key given if the key represents a Nostr + account (usually an individual) or a public Nostr channel. It might + also return "Unknown" if the entity of the key cannot be determined. + E.g. this can be helpful to determine if you want to use + --subscribe-author or --subscribe-channel --subscribe-pubkey [...] - Subscribe to one or more public keys. Specify each public key in form - of 'npub1SomePublicKey'. Alternatively you can use the Hex form of - the private key + Subscribe to one or more public keys. Details: Specify each public + key in form of 'npub1SomePublicKey'. Alternatively you can use the + Hex form of the public key. Use this option to subscribe to an + account, i.e. the key of an individual. See also --subscribe-channel + which are different + --subscribe-author [...] + Subscribe to authors with to one or more public keys of accounts. + Details:: Specify each public key in form of 'npub1SomePublicKey'. + Alternatively you can use the Hex form of the public key. Use this + option to subscribe to a Nostr accounts (usually individuals). + Provide keys that represent accounts (see --get-pubkey-entity). See + also --subscribe-pubkey and --subscribe-channel which are different + --subscribe-channel [...] + Subscribe to public channels with one or more public keys of + channels. Details:: Specify each public key in form of + 'npub1SomePublicKey'. Alternatively you can use the Hex form of the + public key. Sometimes the public key of a public channel is referred + to as channel id. Provide keys that represent public channels (see + --get-pubkey-entity). See also --subscribe-pubkey and + --subscribe-author which are different --limit-number Limit the number of messages to receive when subscribing. By default there is no limit (0) [default: 0] @@ -356,8 +402,7 @@ Options: Limit the messages received to the last N hours when subscribing. Stop receiving N hours in the future. By default there is no limit (0) [default: 0] - -h, --help - Print help information (use `--help` for more detail) + ``` # Other Related Projects diff --git a/VERSION b/VERSION index c5d54ec..7c1886b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.9 +0.0.10 diff --git a/help.txt b/help.txt index 8da4e42..2ff8d59 100644 --- a/help.txt +++ b/help.txt @@ -18,6 +18,16 @@ Options: a "check crates.io" upon request. Your privacy is protected. New release is neither downloaded, nor installed. It just informs you [possible values: check] + --usage + Prints very short help summary. Details:: See also --help, --manual + and --readme + -h, --help + Prints short help. Details:: See also --usage, --manual and --readme + --manual + Prints long help. Details:: See also --usage, --help and --readme + --readme + Prints README.md file, the documenation in Markdown. Details:: See + also --usage, --help and --manual -d, --debug... Overwrite the default log level. If not used, then the default log level set with environment variable 'RUST_LOG' will be used. If used, @@ -124,9 +134,17 @@ Options: its public key, a string in the form of 'npub1...', a Hex key, or an alias from one of your contacts. The first argument is the recipient, all further arguments are texts to be sent. E.g. '-dm - 'npub1SomeStrangeNumbers "First msg" "Second msg"' or 'dm joe "How + "npub1SomeStrangeNumbers" "First msg" "Second msg"' or '--dm joe "How about pizza tonight?"'. See also '--publish' to see how shortcut characters '-' (pipe) and '_' (streamed pipe) are handled + --send-channel-message [...] + Send one or multiple messages to one given channel. The single + destination channel is specified via its hash. See here for a channel + list: https://damus.io/channels/. The first argument is the channel + hash, all further arguments are texts to be sent. E.g. + '-send_channel_message "SomeChannelHash" "First msg" "Second msg"'. + See also '--publish' to see how shortcut characters '-' (pipe) and + '_' (streamed pipe) are handled --add-relay [...] Add one or multiple relays. A relay is specified via a URI that looks like 'wss://some.relay.org'. You can find relays by looking at @@ -203,14 +221,41 @@ Options: --relay [...] Provide one or multiple relays for argument --add-contact. They have the form 'wss://some.relay.org' - --subscribe-author [...] - Subscribe to one or more authors. Specify each author by its public - key in form of 'npub1SomePublicKey'. Alternatively you can use the - Hex form of the private key + --npub-to-hex [...] + Convert one or multiple public keys in Bech32 format ('npub1...') + into the corresponding 'hex' format. Details:: See also --hex-to-npub + --hex-to-npub [...] + Convert one or multiple public keys in 'hex' format into the + corresponding Bech32 ('npub1...') format. Details:: See also + --npub-to-hex + --get-pubkey-entity [...] + Get the entity of one or multiple public keys. Details:: This will + show you for every public key given if the key represents a Nostr + account (usually an individual) or a public Nostr channel. It might + also return "Unknown" if the entity of the key cannot be determined. + E.g. this can be helpful to determine if you want to use + --subscribe-author or --subscribe-channel --subscribe-pubkey [...] - Subscribe to one or more public keys. Specify each public key in form - of 'npub1SomePublicKey'. Alternatively you can use the Hex form of - the private key + Subscribe to one or more public keys. Details: Specify each public + key in form of 'npub1SomePublicKey'. Alternatively you can use the + Hex form of the public key. Use this option to subscribe to an + account, i.e. the key of an individual. See also --subscribe-channel + which are different + --subscribe-author [...] + Subscribe to authors with to one or more public keys of accounts. + Details:: Specify each public key in form of 'npub1SomePublicKey'. + Alternatively you can use the Hex form of the public key. Use this + option to subscribe to a Nostr accounts (usually individuals). + Provide keys that represent accounts (see --get-pubkey-entity). See + also --subscribe-pubkey and --subscribe-channel which are different + --subscribe-channel [...] + Subscribe to public channels with one or more public keys of + channels. Details:: Specify each public key in form of + 'npub1SomePublicKey'. Alternatively you can use the Hex form of the + public key. Sometimes the public key of a public channel is referred + to as channel id. Provide keys that represent public channels (see + --get-pubkey-entity). See also --subscribe-pubkey and + --subscribe-author which are different --limit-number Limit the number of messages to receive when subscribing. By default there is no limit (0) [default: 0] @@ -228,5 +273,4 @@ Options: Limit the messages received to the last N hours when subscribing. Stop receiving N hours in the future. By default there is no limit (0) [default: 0] - -h, --help - Print help information (use `--help` for more detail) + diff --git a/src/main.rs b/src/main.rs index af0fb08..403bf9b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,8 @@ #![allow(unused_imports)] // Todo use atty::Stream; -use clap::{ColorChoice, Parser, ValueEnum}; +use clap::{ColorChoice, CommandFactory, Parser, ValueEnum}; + use directories::ProjectDirs; // use mime::Mime; use chrono::{Duration, Utc}; @@ -31,6 +32,7 @@ use std::fmt::{self, Debug}; use std::fs::{self, File}; use std::io::{self, Read, Write}; use std::net::SocketAddr; +use std::panic; use std::path::{Path, PathBuf}; use std::str::FromStr; use thiserror::Error; @@ -39,6 +41,8 @@ use tracing_subscriber; use update_informer::{registry, Check}; use url::Url; +use bitcoin_hashes::sha256::Hash; + use nostr_sdk::{ nostr::contact::Contact, nostr::event::kind::Kind, @@ -48,6 +52,7 @@ use nostr_sdk::{ nostr::key::{FromBech32, KeyError, Keys, ToBech32}, nostr::message::relay::RelayMessage, nostr::message::subscription::SubscriptionFilter, + nostr::util::time, // nostr::util::nips::nip04::Error as Nip04Error, nostr::Metadata, relay::pool::RelayPoolNotifications, @@ -84,6 +89,8 @@ const CREDENTIALS_FILE_DEFAULT: &str = "credentials.json"; // const TIMEOUT_DEFAULT: u64 = 60; /// default POW difficulty const POW_DIFFICULTY_DEFAULT: u8 = 20; +/// URL for README.md file downloaded for --readme +const URL_README: &str = "https://raw.githubusercontent.com/8go/nostr-commander-rs/main/README.md"; /// The enumerator for Errors #[derive(Error, Debug)] @@ -118,6 +125,9 @@ pub enum Error { #[error("Add Relay Failed")] AddRelayFailed, + #[error("Conversion Failed")] + ConversionFailed, + #[error("Publish Failed")] PublishFailed, @@ -130,18 +140,27 @@ pub enum Error { #[error("Send Failed")] SendFailed, + #[error("Send Channel Failed")] + SendChannelFailed, + #[error("Listen Failed")] ListenFailed, #[error("Subscription Failed")] SubscriptionFailed, + #[error("Get Entity Failed")] + GetEntityFailed, + #[error("Invalid Client Connection")] InvalidClientConnection, #[error("Invalid Key")] InvalidKey, + #[error("Invalid Hash")] + InvalidHash, + #[error("Unknown CLI parameter")] UnknownCliParameter, @@ -324,12 +343,14 @@ impl fmt::Display for Output { /// and see if you can contribute code to improve this tool. /// Safe! #[derive(Clone, Debug, Parser)] -#[command(author, version, next_line_help = true, +#[command(author, version, + next_line_help = true, bin_name = get_prog_without_ext(), color = ColorChoice::Always, term_width = 79, after_help = "", disable_version_flag = true, + disable_help_flag = true, )] pub struct Args { // This is an internal field used to store credentials. @@ -353,6 +374,26 @@ pub struct Args { #[arg(short, long, value_name = "CHECK")] version: Option>, + /// Prints very short help summary. + /// Details:: See also --help, --manual and --readme. + #[arg(long)] + usage: bool, + + /// Prints short help. + /// Details:: See also --usage, --manual and --readme. + #[arg(short, long)] + help: bool, + + /// Prints long help. + /// Details:: See also --usage, --help and --readme. + #[arg(long)] + manual: bool, + + /// Prints README.md file, the documenation in Markdown. + /// Details:: See also --usage, --help and --manual. + #[arg(long)] + readme: bool, + /// Overwrite the default log level. If not used, then the default /// log level set with environment variable 'RUST_LOG' will be used. /// If used, log level will be set to 'DEBUG' and debugging information @@ -517,13 +558,26 @@ pub struct Args { /// string in the form of 'npub1...', a Hex key, or an alias from /// one of your contacts. The first argument /// is the recipient, all further arguments are texts to be - /// sent. E.g. '-dm 'npub1SomeStrangeNumbers "First msg" "Second msg"' - /// or 'dm joe "How about pizza tonight?"'. + /// sent. E.g. '-dm "npub1SomeStrangeNumbers" "First msg" "Second msg"' + /// or '--dm joe "How about pizza tonight?"'. /// See also '--publish' to see how shortcut characters /// '-' (pipe) and '_' (streamed pipe) are handled. #[arg(long, alias = "direct", value_name = "KEY+MSGS", num_args(0..), )] dm: Vec, + /// Send one or multiple messages to one given channel. + /// The single destination channel is specified via its hash. + /// See here for a channel list: https://damus.io/channels/. + /// The first argument + /// is the channel hash, all further arguments are texts to be + /// sent. E.g. + /// '-send_channel_message "SomeChannelHash" "First msg" "Second msg"'. + // or '--send_channel_message joe "How about pizza tonight?"'. + /// See also '--publish' to see how shortcut characters + /// '-' (pipe) and '_' (streamed pipe) are handled. + #[arg(long, alias = "chan", value_name = "HASH+MSGS", num_args(0..), )] + send_channel_message: Vec, + /// Add one or multiple relays. A relay is specified via a URI /// that looks like 'wss://some.relay.org'. You can find relays /// by looking at https://github.com/aljazceru/awesome-nostr#instances. @@ -647,21 +701,63 @@ pub struct Args { #[arg(long, value_name = "RELAY", num_args(0..), )] relay: Vec, - /// Subscribe to one or more authors. Specify each author by its + /// Convert one or multiple public keys in Bech32 format ('npub1...') into + /// the corresponding 'hex' format. + /// Details:: See also --hex-to-npub. + #[arg(long, value_name = "KEY", num_args(0..), )] + npub_to_hex: Vec, + + /// Convert one or multiple public keys in 'hex' format into + /// the corresponding Bech32 ('npub1...') format. + /// Details:: See also --npub-to-hex. + #[arg(long, value_name = "KEY", num_args(0..), )] + hex_to_npub: Vec, + + /// Get the entity of one or multiple public keys. + /// Details:: This will show you + /// for every public key given if the key represents a Nostr account + /// (usually an individual) or a public Nostr channel. It might also + /// return "Unknown" if the entity of the key cannot be determined. + /// E.g. this can be helpful to determine if you want to use + /// --subscribe-author or --subscribe-channel. + #[arg(long, value_name = "KEY", num_args(0..), )] + get_pubkey_entity: Vec, + + /// Subscribe to one or more public keys. + /// Details: Specify each + /// public key in form of 'npub1SomePublicKey'. + /// Alternatively you can use the Hex form of the public key. + /// Use this option to subscribe to an account, i.e. the key of + /// an individual. + /// See also --subscribe-channel which are different. + #[arg(long, value_name = "KEY", num_args(0..), )] + subscribe_pubkey: Vec, + + /// Subscribe to authors with to one or more public keys of accounts. + /// Details:: Specify each /// public key in form of 'npub1SomePublicKey'. - /// Alternatively you can use the Hex form of the private key. + /// Alternatively you can use the Hex form of the public key. + /// Use this option to subscribe to a Nostr accounts (usually individuals). + /// Provide keys that represent accounts (see --get-pubkey-entity). + /// See also --subscribe-pubkey and --subscribe-channel which are different. #[arg(long, value_name = "KEY", num_args(0..), )] subscribe_author: Vec, - /// Subscribe to one or more public keys. Specify each + /// Subscribe to public channels with one or more public keys of channels. + /// Details:: Specify each /// public key in form of 'npub1SomePublicKey'. - /// Alternatively you can use the Hex form of the private key. + /// Alternatively you can use the Hex form of the public key. + /// Sometimes the public key of a public channel is referred to as + /// channel id. + /// Provide keys that represent public channels (see --get-pubkey-entity). + /// See also --subscribe-pubkey and --subscribe-author which are different. #[arg(long, value_name = "KEY", num_args(0..), )] - subscribe_pubkey: Vec, + subscribe_channel: Vec, + // todo: unsubscribe_pubkey // todo unsubscribe_author - // todo mpub1-to-hex - // todo hex-to-npub1 + // todo: unsubscribe_channel + // /// Limit the number of messages to receive when subscribing. /// By default there is no limit (0). @@ -701,6 +797,10 @@ impl Args { pub fn new() -> Args { Args { creds: Credentials::new(), + usage: false, + help: false, + manual: false, + readme: false, contribute: false, version: None, debug: 0u8, @@ -719,6 +819,7 @@ impl Args { publish: Vec::new(), publish_pow: Vec::new(), dm: Vec::new(), + send_channel_message: Vec::new(), add_relay: Vec::new(), // tag: Vec::new(), show_metadata: false, @@ -736,8 +837,12 @@ impl Args { alias: Vec::new(), key: Vec::new(), relay: Vec::new(), - subscribe_author: Vec::new(), + npub_to_hex: Vec::new(), + hex_to_npub: Vec::new(), + get_pubkey_entity: Vec::new(), subscribe_pubkey: Vec::new(), + subscribe_author: Vec::new(), + subscribe_channel: Vec::new(), limit_number: 0, limit_days: 0, limit_hours: 0, @@ -757,8 +862,9 @@ pub struct Credentials { relays: Vec, metadata: Metadata, contacts: Vec, - subscribed_authors: Vec, subscribed_pubkeys: Vec, + subscribed_authors: Vec, + subscribed_channels: Vec, } impl AsRef for Credentials { @@ -783,8 +889,9 @@ impl Credentials { relays: Vec::new(), metadata: Metadata::new(), contacts: Vec::new(), - subscribed_authors: Vec::new(), subscribed_pubkeys: Vec::new(), + subscribed_authors: Vec::new(), + subscribed_channels: Vec::new(), } } @@ -906,6 +1013,40 @@ fn get_prog_without_ext() -> &'static str { // get_pkg_name() // without -rs suffix } +/// Prints the usage info +pub fn usage() { + let help_str = Args::command().render_usage().to_string(); + println!("{}", &help_str); +} + +/// Prints the short help +pub fn help() { + let help_str = Args::command().render_help().to_string(); + println!("{}", &help_str); +} + +/// Prints the long help +pub fn manual() { + let help_str = Args::command().render_long_help().to_string(); + println!("{}", &help_str); +} + +/// Prints the README.md file +pub async fn readme() { + match reqwest::get(URL_README).await { + Ok(resp) => { + debug!("Got README.md file from URL {:?}.", URL_README); + println!("{}", resp.text().await.unwrap()) + } + Err(ref e) => { + println!( + "Error getting README.md from {:#?}. Reported error {:?}.", + URL_README, e + ); + } + }; +} + /// Prints the version information pub fn version() { println!(); @@ -1776,6 +1917,149 @@ pub(crate) async fn cli_dm(client: &Client, ap: &mut Args) -> Result<(), Error> } } +/// Send messages to one channel. +pub(crate) async fn send_channel_messages( + client: &Client, + notes: &[String], // msgs + channel_id: Hash, + relay_url: Url, +) -> Result<(), Error> { + trace!("send_channel_messages {:?} {:?}.", notes, channel_id); + let mut err_count = 0usize; + let num = notes.len(); + let mut i = 0; + while i < num { + let note = ¬es[i]; + trace!("send_dms: {:?}", note); + if note.is_empty() { + info!("Skipping empty text note."); + i += 1; + continue; + }; + if note == "--" { + info!("Skipping '--' text note as these are used to separate arguments."); + i += 1; + continue; + }; + // - map to - (stdin pipe) + // \- maps to text r'-', a 1-letter message + let fnote = if note == r"-" { + let mut line = String::new(); + if atty::is(Stream::Stdin) { + print!("Message: "); + std::io::stdout() + .flush() + .expect("error: could not flush stdout"); + io::stdin().read_line(&mut line)?; + } else { + io::stdin().read_to_string(&mut line)?; + } + line + } else if note == r"_" { + let mut eof = false; + while !eof { + let mut line = String::new(); + match io::stdin().read_line(&mut line) { + // If this function returns Ok(0), the stream has reached EOF. + Ok(n) => { + if n == 0 { + eof = true; + debug!("Reached EOF of pipe stream."); + } else { + debug!( + "Read {n} bytes containing \"{}\\n\" from pipe stream.", + trim_newline(&mut line.clone()) + ); + match client.send_channel_msg(channel_id, relay_url.clone(), &line).await { + Ok(()) => debug!( + "send_channel_msg number {:?} from pipe stream sent successfully. {:?}, sent to {:?}", + i, &line, channel_id + ), + Err(ref e) => { + err_count += 1; + error!( + "send_channel_msg number {:?} from pipe stream failed. {:?}, sent to {:?}", + i, &line, channel_id + ); + } + } + } + } + Err(ref e) => { + err_count += 1; + error!("Error: reading from pipe stream reported {}", e); + } + } + } + "".to_owned() + } else if note == r"\-" { + "-".to_string() + } else if note == r"\_" { + "_".to_string() + } else if note == r"\-\-" { + "--".to_string() + } else if note == r"\-\-\-" { + "---".to_string() + } else { + note.to_string() + }; + if fnote.is_empty() { + info!("Skipping empty text note."); + i += 1; + continue; + } + + match client + .send_channel_msg(channel_id, relay_url.clone(), &fnote) + .await + { + Ok(()) => debug!( + "send_channel_msg message number {:?} sent successfully. {:?}, sent to {:?}.", + i, &fnote, channel_id + ), + Err(ref e) => { + err_count += 1; + error!( + "send_channel_msg message number {:?} failed. {:?}, sent to {:?}.", + i, &fnote, channel_id + ); + } + } + i += 1; + } + if err_count != 0 { + Err(Error::SendChannelFailed) + } else { + Ok(()) + } +} + +/// Handle the --send-channel-message CLI argument +/// Publish messages to one channel. +pub(crate) async fn cli_send_channel_message(client: &Client, ap: &mut Args) -> Result<(), Error> { + let num = ap.send_channel_message.len(); + if num < 2 { + return Err(Error::MissingCliParameter); + } + // todo: check if hash is valid, doable? + match Hash::from_str(&ap.send_channel_message[0]) { + Ok(hash) => { + let notes = &ap.send_channel_message[1..]; + // todo: any relay is fine? + let relay_url: Url = ap.creds.relays[0].clone(); + send_channel_messages(client, notes, hash, relay_url).await + } + Err(ref e) => { + error!( + "Error: Not a valid hash (channel id). Cannot send this channel message. Aborting. Hash {:?}, 1st Msg {:?} ", + ap.send_channel_message[0], + ap.send_channel_message[1] + ); + Err(Error::InvalidHash) + } + } +} + /// Is key in subscribed_authors list? pub(crate) fn is_subscribed_author(ap: &Args, pkey: &XOnlyPublicKey) -> bool { ap.creds.subscribed_authors.contains(pkey) @@ -1913,40 +2197,6 @@ pub(crate) async fn cli_remove_contact(client: &Client, ap: &mut Args) -> Result Ok(()) } -/// Handle the --subscribe-author CLI argument, moving authors from CLI args into creds data structure -pub(crate) async fn cli_subscribe_author(client: &mut Client, ap: &mut Args) -> Result<(), Error> { - let mut err_count = 0usize; - let num = ap.subscribe_author.len(); - let mut authors = Vec::new(); - let mut i = 0; - while i < num { - match str_to_pubkey(&ap.subscribe_author[i]) { - Ok(pkey) => { - authors.push(pkey); - debug!( - "Valid key added to subscription filter. Key {:?}, {:?}.", - &ap.subscribe_author[i], pkey - ); - } - Err(ref e) => { - error!( - "Error: Invalid key {:?}. Not added to subscription filter.", - &ap.subscribe_author[i] - ); - err_count += 1; - } - } - i += 1; - } - ap.creds.subscribed_authors.append(&mut authors); - ap.creds.subscribed_authors.dedup_by(|a, b| a == b); - if err_count != 0 { - Err(Error::SubscriptionFailed) - } else { - Ok(()) - } -} - /// Convert npub1... Bech32 key or Hex key or contact alias into a XOnlyPublicKey /// Returns Error if neither valid Bech32, nor Hex key, nor contact alias. pub(crate) fn cstr_to_pubkey(ap: &Args, s: &str) -> Result { @@ -1985,6 +2235,206 @@ pub(crate) fn str_to_pubkey(s: &str) -> Result { } } +/// Convert npub1... Bech32 key or Hex key into a npub+hex pair as Vector. +/// s ... input, npub ... output, hex ... output. +/// Returns Error if neither valid Bech32 nor Hex key. +pub(crate) fn str_to_pubkeys(s: &str) -> Result<(String, String), Error> { + match Keys::from_bech32_public_key(s) { + Ok(keys) => { + debug!( + "Valid key in Bech32 format: Npub {:?}, Hex {:?}", + s, + keys.public_key().to_string() + ); + let npub = s.to_owned(); + let hex = keys.public_key().to_string(); + return Ok((npub, hex)); + } + Err(ref e) => match XOnlyPublicKey::from_str(s) { + Ok(pkey) => { + debug!( + "Valid key in Hex format: Hex {:?}, Npub {:?}", + s, + pkey.to_bech32().unwrap() + ); + let npub = pkey.to_bech32().unwrap(); + let hex = s.to_owned(); + return Ok((npub, hex)); + } + Err(ref e) => { + error!("Error: Invalid key {:?}. Reported error: {:?}.", s, e); + return Err(Error::InvalidKey); + } + }, + } +} + +/// Handle the --cli_npub_to_hex CLI argument +pub(crate) fn cli_npub_to_hex(ap: &Args) -> Result<(), Error> { + let mut err_count = 0usize; + let num = ap.npub_to_hex.len(); + let mut i = 0; + while i < num { + match str_to_pubkeys(&ap.npub_to_hex[i]) { + Ok((npub, hex)) => { + debug!("Valid key. Npub {:?}, Hex: {:?}.", &npub, &hex); + print_json( + &json!({ + "npub": npub, + "hex": hex, + }), + ap.output, + 0, + "", + ); + } + Err(ref e) => { + error!( + "Error: Invalid key {:?}. Not added to subscription filter.", + &ap.npub_to_hex[i] + ); + print_json( + &json!({ + "npub": ap.npub_to_hex[i], + "error": "invalid key", + }), + ap.output, + 0, + "", + ); + err_count += 1; + } + } + i += 1; + } + if err_count != 0 { + Err(Error::ConversionFailed) + } else { + Ok(()) + } +} + +/// Handle the --cli_hex_to_npub CLI argument +pub(crate) fn cli_hex_to_npub(ap: &Args) -> Result<(), Error> { + let mut err_count = 0usize; + let num = ap.hex_to_npub.len(); + let mut i = 0; + while i < num { + match str_to_pubkeys(&ap.hex_to_npub[i]) { + Ok((npub, hex)) => { + debug!("Valid key. Npub {:?}, Hex: {:?}.", &npub, &hex); + print_json( + &json!({ + "npub": npub, + "hex": hex, + }), + ap.output, + 0, + "", + ); + } + Err(ref e) => { + error!( + "Error: Invalid key {:?}. Not added to subscription filter.", + &ap.hex_to_npub[i] + ); + print_json( + &json!({ + "hex": ap.hex_to_npub[i], + "error": "invalid key", + }), + ap.output, + 0, + "", + ); + err_count += 1; + } + } + i += 1; + } + if err_count != 0 { + Err(Error::ConversionFailed) + } else { + Ok(()) + } +} + +/// Handle the --cli_get_pubkey_entity CLI argument +pub(crate) async fn cli_get_pubkey_entity(client: &Client, ap: &mut Args) -> Result<(), Error> { + let mut err_count = 0usize; + let num = ap.get_pubkey_entity.len(); + let mut i = 0; + while i < num { + match str_to_pubkey(&ap.get_pubkey_entity[i]) { + Ok(pkey) => { + debug!( + "Valid key. Key {:?}, Hex: {:?}.", + &ap.subscribe_pubkey[i], + pkey.to_string() + ); + match client.get_entity_of_pubkey(pkey).await { + Ok(entity) => { + debug!( + "Valid key. Key {:?}, Hex: {:?}, Entity: {:?}", + &ap.subscribe_pubkey[i], + pkey.to_string(), + entity + ); + print_json( + &json!({ + "hex": pkey.to_string(), + "entity": format!("{:?}",entity), + }), + ap.output, + 0, + "", + ); + } + Err(ref e) => { + debug!( + "Valid key. Key {:?}, Hex: {:?}, Entity error: {:?}", + &ap.subscribe_pubkey[i], + pkey.to_string(), + e + ); + print_json( + &json!({ + "hex": pkey.to_string(), + "error": format!("{:?}",e), + }), + ap.output, + 0, + "", + ); + } + } + } + Err(ref e) => { + error!( + "Error: Invalid key {:?}. No attempt made to determine entity.", + &ap.get_pubkey_entity[i] + ); + print_json( + &json!({ + "key": ap.get_pubkey_entity[i], + "error": "invalid key", + }), + ap.output, + 0, + "", + ); + err_count += 1; + } + } + i += 1; + } + if err_count != 0 { + Err(Error::GetEntityFailed) + } else { + Ok(()) + } +} + /// Handle the --subscribe-pubkey CLI argument, moving pkeys from CLI args into creds data structure pub(crate) async fn cli_subscribe_pubkey(client: &mut Client, ap: &mut Args) -> Result<(), Error> { let mut err_count = 0usize; @@ -2020,6 +2470,75 @@ pub(crate) async fn cli_subscribe_pubkey(client: &mut Client, ap: &mut Args) -> } } +/// Handle the --subscribe-author CLI argument, moving authors from CLI args into creds data structure +pub(crate) async fn cli_subscribe_author(client: &mut Client, ap: &mut Args) -> Result<(), Error> { + let mut err_count = 0usize; + let num = ap.subscribe_author.len(); + let mut authors = Vec::new(); + let mut i = 0; + while i < num { + match str_to_pubkey(&ap.subscribe_author[i]) { + Ok(pkey) => { + authors.push(pkey); + debug!( + "Valid key added to subscription filter. Key {:?}, {:?}.", + &ap.subscribe_author[i], pkey + ); + } + Err(ref e) => { + error!( + "Error: Invalid key {:?}. Not added to subscription filter.", + &ap.subscribe_author[i] + ); + err_count += 1; + } + } + i += 1; + } + ap.creds.subscribed_authors.append(&mut authors); + ap.creds.subscribed_authors.dedup_by(|a, b| a == b); + if err_count != 0 { + Err(Error::SubscriptionFailed) + } else { + Ok(()) + } +} + +/// Handle the --subscribe-channel CLI argument, moving pkeys from CLI args into creds data structure +pub(crate) async fn cli_subscribe_channel(client: &mut Client, ap: &mut Args) -> Result<(), Error> { + let mut err_count = 0usize; + let num = ap.subscribe_channel.len(); + let mut pubkeys = Vec::new(); + let mut i = 0; + while i < num { + match str_to_pubkey(&ap.subscribe_channel[i]) { + Ok(pkey) => { + pubkeys.push(pkey); + debug!( + "Valid key added to subscription filter. Key {:?}, Hex: {:?}.", + &ap.subscribe_channel[i], + pkey.to_string() + ); + } + Err(ref e) => { + error!( + "Error: Invalid key {:?}. Not added to subscription filter.", + &ap.subscribe_channel[i] + ); + err_count += 1; + } + } + i += 1; + } + ap.creds.subscribed_channels.append(&mut pubkeys); + ap.creds.subscribed_channels.dedup_by(|a, b| a == b); + if err_count != 0 { + Err(Error::SubscriptionFailed) + } else { + Ok(()) + } +} + /// Utility function to print JSON object as JSON or as plain text /// depth: depth in nesting, on first call use 0. // see https://github.com/serde-rs/json @@ -2069,6 +2588,7 @@ pub(crate) fn print_json(jsonv: &Value, output: Output, depth: u32, separator: & } Output::JsonSpec => (), _ => { + // This can panic if output is piped and pipe is broken by receiving process println!("{}", jsonv.to_string(),); } } @@ -2139,6 +2659,22 @@ async fn main() -> Result<(), Error> { Some(None) => crate::version(), // print version Some(Some(Version::Check)) => crate::version_check(), } + if ap.usage { + crate::usage(); + return Ok(()); + }; + if ap.help { + crate::help(); + return Ok(()); + }; + if ap.manual { + crate::manual(); + return Ok(()); + }; + if ap.readme { + crate::readme().await; + return Ok(()); + }; if ap.contribute { crate::contribute(); }; @@ -2196,6 +2732,34 @@ async fn main() -> Result<(), Error> { if ap.whoami { cli_whoami(&ap)?; } + // npub_to_hex + if !ap.npub_to_hex.is_empty() { + match cli_npub_to_hex(&ap) { + Ok(()) => { + info!("Converting keys from npub to hex successful."); + } + Err(ref e) => { + error!( + "Converting keys from npub to hex failed. Reported error is: {:?}", + e + ); + } + } + } + // hex_to_npub + if !ap.hex_to_npub.is_empty() { + match cli_hex_to_npub(&ap) { + Ok(()) => { + info!("Converting keys from hex to npub successful."); + } + Err(ref e) => { + error!( + "Converting keys from hex to npub failed. Reported error is: {:?}", + e + ); + } + } + } // Create new client let mut client = Client::new(&my_keys); @@ -2227,26 +2791,42 @@ async fn main() -> Result<(), Error> { || !ap.publish_pow.is_empty() || !ap.publish.is_empty() || !ap.dm.is_empty() - || !ap.subscribe_author.is_empty() + || !ap.send_channel_message.is_empty() || !ap.subscribe_pubkey.is_empty() + || !ap.subscribe_author.is_empty() + || !ap.subscribe_channel.is_empty() + || !ap.get_pubkey_entity.is_empty() { // todo avoid connect_...() call if not relay action is needed and everything can be done locally. // todo avoid connect...() if no client is needed. // - // Connect to relays, WAIT for connection, and keep connection alive - // todo only use the wait version if there is no -l and there is some publish // also do a wait on create-user ? - // match client.connect().await { - match client.connect_and_wait().await { - Ok(()) => { - info!("connect successful."); + if ap.listen { + match client.connect().await { + Ok(()) => { + info!("connect successful."); + } + Err(ref e) => { + error!( + "connect failed. Could not connect to relays. Reported error is: {:?}", + e + ); + return Err(Error::CannotConnectToRelays); + } } - Err(ref e) => { - error!( - "connect failed. Could not connect to relays. Reported error is: {:?}", + } else { + // Connect to relays, WAIT for connection, and keep connection alive + match client.connect_and_wait().await { + Ok(()) => { + info!("connect_and_wait successful."); + } + Err(ref e) => { + error!( + "connect_and_wait failed. Could not connect to relays. Reported error is: {:?}", e ); - return Err(Error::CannotConnectToRelays); + return Err(Error::CannotConnectToRelays); + } } } } @@ -2314,6 +2894,18 @@ async fn main() -> Result<(), Error> { } // ap.creds.save(get_credentials_actual_path(&ap))?; // do it later + // Get pubkey entity + if !ap.get_pubkey_entity.is_empty() { + match crate::cli_get_pubkey_entity(&client, &mut ap).await { + Ok(()) => { + info!("get_pubkey_entity successful."); + } + Err(ref e) => { + error!("get_pubkey_entity failed. Reported error is: {:?}", e); + } + } + } + // Publish a text note if !ap.publish.is_empty() { match crate::cli_publish(&client, &mut ap).await { @@ -2347,7 +2939,57 @@ async fn main() -> Result<(), Error> { } } } + // Send channel messages + if !ap.send_channel_message.is_empty() { + match crate::cli_send_channel_message(&client, &mut ap).await { + Ok(()) => { + info!("send-channel-message successful."); + } + Err(ref e) => { + error!("send-channel-message failed. Reported error is: {:?}", e); + } + } + } + // Subscribe keys + if !ap.subscribe_pubkey.is_empty() { + match crate::cli_subscribe_pubkey(&mut client, &mut ap).await { + Ok(()) => { + debug!("subscribe_pubkey successful. Subscriptions synchronized with credentials file."); + } + Err(ref e) => { + error!("subscribe_pubkey failed. Reported error is: {:?}", e); + } + } + } + if !ap.creds.subscribed_pubkeys.is_empty() && ap.listen { + let mut ksf: SubscriptionFilter; + ksf = SubscriptionFilter::new().pubkeys(ap.creds.subscribed_pubkeys.clone()); + if ap.limit_number != 0 { + ksf = ksf.limit(ap.limit_number); + } + if ap.limit_days != 0 { + ksf = ksf.since((Utc::now() - Duration::days(ap.limit_days)).timestamp() as u64); + } + if ap.limit_hours != 0 { + ksf = ksf.since((Utc::now() - Duration::hours(ap.limit_hours)).timestamp() as u64); + } + if ap.limit_future_days != 0 { + ksf = ksf.until((Utc::now() - Duration::days(ap.limit_future_days)).timestamp() as u64); + } + if ap.limit_future_hours != 0 { + ksf = + ksf.until((Utc::now() - Duration::hours(ap.limit_future_hours)).timestamp() as u64); + } + match client.subscribe(vec![ksf]).await { + Ok(()) => { + info!("subscribe to pubkeys successful."); + } + Err(ref e) => { + error!("subscribe to pubkeys failed. Reported error is: {:?}", e); + } + } + } // Subscribe authors if !ap.subscribe_author.is_empty() { match crate::cli_subscribe_author(&mut client, &mut ap).await { @@ -2387,42 +3029,42 @@ async fn main() -> Result<(), Error> { } } } - // Subscribe keys - if !ap.subscribe_pubkey.is_empty() { - match crate::cli_subscribe_pubkey(&mut client, &mut ap).await { + // Subscribe channels + if !ap.subscribe_channel.is_empty() { + match crate::cli_subscribe_channel(&mut client, &mut ap).await { Ok(()) => { - debug!("subscribe_pubkey successful. Subscriptions synchronized with credentials file."); + debug!("subscribe_channel successful. Subscriptions synchronized with credentials file."); } Err(ref e) => { - error!("subscribe_pubkey failed. Reported error is: {:?}", e); + error!("subscribe_channel failed. Reported error is: {:?}", e); } } } - if !ap.creds.subscribed_pubkeys.is_empty() && ap.listen { - let mut ksf: SubscriptionFilter; - ksf = SubscriptionFilter::new().pubkeys(ap.creds.subscribed_pubkeys.clone()); + if !ap.creds.subscribed_channels.is_empty() && ap.listen { + let mut csf: SubscriptionFilter; + csf = SubscriptionFilter::new().events(ap.creds.subscribed_channels.clone()); if ap.limit_number != 0 { - ksf = ksf.limit(ap.limit_number); + csf = csf.limit(ap.limit_number); } if ap.limit_days != 0 { - ksf = ksf.since((Utc::now() - Duration::days(ap.limit_days)).timestamp() as u64); + csf = csf.since((Utc::now() - Duration::days(ap.limit_days)).timestamp() as u64); } if ap.limit_hours != 0 { - ksf = ksf.since((Utc::now() - Duration::hours(ap.limit_hours)).timestamp() as u64); + csf = csf.since((Utc::now() - Duration::hours(ap.limit_hours)).timestamp() as u64); } if ap.limit_future_days != 0 { - ksf = ksf.until((Utc::now() - Duration::days(ap.limit_future_days)).timestamp() as u64); + csf = csf.until((Utc::now() - Duration::days(ap.limit_future_days)).timestamp() as u64); } if ap.limit_future_hours != 0 { - ksf = - ksf.until((Utc::now() - Duration::hours(ap.limit_future_hours)).timestamp() as u64); + csf = + csf.until((Utc::now() - Duration::hours(ap.limit_future_hours)).timestamp() as u64); } - match client.subscribe(vec![ksf]).await { + match client.subscribe(vec![csf]).await { Ok(()) => { - info!("subscribe to pubkeys successful."); + info!("subscribe to channels successful."); } Err(ref e) => { - error!("subscribe to pubkeys failed. Reported error is: {:?}", e); + error!("subscribe to channels failed. Reported error is: {:?}", e); } } } @@ -2434,7 +3076,8 @@ async fn main() -> Result<(), Error> { // || !ap.creds.subscribed_authors.is_empty() // || !ap.creds.subscribed_pubkeys.is_empty() { - let num = ap.publish.len() + ap.publish_pow.len() + ap.dm.len(); + let num = + ap.publish.len() + ap.publish_pow.len() + ap.dm.len() + ap.send_channel_message.len(); if num == 1 { info!( "You should be receiving {:?} 'OK' message with event id for the notice once it has been relayed.", @@ -2449,7 +3092,7 @@ async fn main() -> Result<(), Error> { // Handle notifications match client .handle_notifications(|notification| { - trace!("Notification: {:?}", notification); + debug!("Notification: {:?}", notification); match notification { ReceivedEvent(ev) => { debug!("Event-Event: content {:?}, kind {:?}", ev.content, ev.kind); @@ -2460,10 +3103,10 @@ async fn main() -> Result<(), Error> { RelayMessage::Ok {event_id, status, message } => { // Notification: ReceivedMessage(Ok { event_id: 123, status: true, message: "" }) // confirmation of notice having been relayed - info!("Message-OK: Notice or DM was relayed. Event id is {:?}. Status is {:?} and message is {:?}. You can investigate this event by looking it up on https://nostr.com/e/{}", event_id, status, message, event_id.to_string()); + info!("Message-OK: Notice, DM or message was relayed. Event id is {:?}. Status is {:?} and message is {:?}. You can investigate this event by looking it up on https://nostr.com/e/{}", event_id, status, message, event_id.to_string()); print_json( &json!({"event_type": "RelayMessage::Ok", - "event_type_meaning": "Notice or DM was relayed successfully.", + "event_type_meaning": "Notice, DM or message was relayed successfully.", "event_id": event_id, "status": status, "message": message, @@ -2500,6 +3143,7 @@ async fn main() -> Result<(), Error> { }, Ok(TagKind::E) => (), Ok(TagKind::Nonce) => (), + Ok(TagKind::Delegation) => todo!(), Err(_) => () } } @@ -2535,7 +3179,7 @@ async fn main() -> Result<(), Error> { ); }, Kind::Base(KindBase::ChannelMessage) => { - warn!("Subscription by {} ({}): content {:?}, kind {:?}, from pubkey {:?}", key_author, tags, event.content, event.kind, get_contact_alias_or_keystr_by_key(&ap, event.pubkey)); + info!("Subscription by {} ({}): content {:?}, kind {:?}, from pubkey {:?}", key_author, tags, event.content, event.kind, get_contact_alias_or_keystr_by_key(&ap, event.pubkey)); print_json( &json!({ "event_type": "RelayMessage::Event",