├── .devcontainer └── devcontainer.json ├── .dockerignore ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── docs ├── EXAMPLES.md └── INSTALL.md ├── flake.lock ├── flake.nix ├── resources └── contact_list.csv ├── rust-toolchain.toml └── src ├── main.rs ├── sub_commands ├── award_badge.rs ├── broadcast_events.rs ├── convert_key.rs ├── create_badge.rs ├── create_public_channel.rs ├── custom_event.rs ├── delete_event.rs ├── delete_profile.rs ├── generate_keypair.rs ├── hide_public_channel_message.rs ├── list_events.rs ├── mod.rs ├── mute_publickey.rs ├── profile_badges.rs ├── publish_contactlist_csv.rs ├── react.rs ├── send_channel_message.rs ├── set_channel_metadata.rs ├── set_metadata.rs ├── text_note.rs ├── user_status.rs └── vanity.rs └── utils.rs /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/rust 3 | { 4 | "name": "Rust", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/rust:0-1-bullseye" 7 | 8 | // Use 'mounts' to make the cargo cache persistent in a Docker Volume. 9 | // "mounts": [ 10 | // { 11 | // "source": "devcontainer-cargo-cache-${devcontainerId}", 12 | // "target": "/usr/local/cargo", 13 | // "type": "volume" 14 | // } 15 | // ] 16 | 17 | // Features to add to the dev container. More info: https://containers.dev/features. 18 | // "features": {}, 19 | 20 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 21 | // "forwardPorts": [], 22 | 23 | // Use 'postCreateCommand' to run commands after the container is created. 24 | // "postCreateCommand": "rustc --version", 25 | 26 | // Configure tool-specific properties. 27 | // "customizations": {}, 28 | 29 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 30 | // "remoteUser": "root" 31 | } 32 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | *.json -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.5.0 - 2023-09-05 4 | 5 | ### Added 6 | - Support for NIP-58 Badges by [@0xtrr](https://github.com/0xtrr) 7 | - Add support for a tags in list events command by [@0xtrr](https://github.com/0xtrr) 8 | - Add support for NIP-315 user statuses by [@0xtrr](https://github.com/0xtrr) 9 | 10 | ### Changed 11 | - Upgrade nostr-sdk from 0.22.0 to 0.23.0 by [@0xtrr](https://github.com/0xtrr) 12 | 13 | ### Fixed 14 | 15 | ## 0.4.0 - 2023-06-09 16 | 17 | ### Added 18 | - Save output from list-events to file by [@thesimplekid](https://github.com/thesimplekid) 19 | - Broadcast events from json file by [@thesimplekid](https://github.com/thesimplekid) 20 | - Add option to print keys as hex instead of bech32 by [@thesimplekid](https://github.com/thesimplekid) 21 | - Add LUD-16 to update metadata command by [@w3irdrobot](https://github.com/w3irdrobot) 22 | - Add support for creating NIP-57 events (Zaps) by [@0xtrr](https://github.com/0xtrr) 23 | - Add support for custom events with arbitrary kind, content and tags by [@0xtrr](https://github.com/0xtrr) 24 | - Add timeout argument for list-events command by [@thesimplekid](https://github.com/thesimplekid) 25 | - Add bech32 support pubkeys and events for list-events command by [@thesimplekid](https://github.com/thesimplekid) 26 | 27 | 28 | ### Changed 29 | - Upgrade nostr-sdk from 0.18 to 0.20 by [@thesimplekid](https://github.com/thesimplekid) 30 | - Upgrade nostr-sdk from 0.20 to 0.21 by [@0xtrr](https://github.com/0xtrr) 31 | - Upgrade nostr-sdk from 0.21 to 0.22 by [@0xtrr](https://github.com/0xtrr) 32 | - Remove bitcoin dependency by [@thesimplekid](https://github.com/thesimplekid) 33 | 34 | ### Fixed 35 | - Print events as valid json in list-events command by [@thesimplekid](https://github.com/thesimplekid) 36 | 37 | ## 0.3.0 - 2023-02-20 38 | 39 | ### Added 40 | - NIP-28 support. 41 | - Add expiration tag to text-note by [@thesimplekid](https://github.com/thesimplekid). 42 | - Add Dockerfile by [@bijeebuss](https://github.com/bijeebuss). 43 | - Add .devcontainer by [@bijeebuss](https://github.com/bijeebuss). 44 | - Add encoding/decoding of nprofile strings 45 | - Add NIP-14 (subject tags) support to text notes 46 | - Add support for encoding/decoding bech32 encoded nchannel ids 47 | 48 | ### Changed 49 | - Upgrade dependency Clap from 4.0.22 to 4.1.6. 50 | - Big rewrite by [@yukibtc](https://github.com/yukibtc) to replace nostr_rust with [nostr-sdk](https://github.com/rust-nostr/nostr). 51 | - Print nchannel id when creating new public channel 52 | 53 | ### Fixed 54 | - Update typo in examples in Readme by [@gourcetools](https://github.com/gourcetools). 55 | - parse_key function misbehaved after nostr-sdk refactoring. 56 | - Refactor/code cleanup in "list events" code by [@thesimplekid](https://github.com/thesimplekid). 57 | - Pretty print events in "list events" command output by [@thesimplekid](https://github.com/thesimplekid). 58 | 59 | 60 | ## 0.2.0 - 2023-01-08 61 | 62 | ### Added 63 | - Support for bech32 encoded keys and notes in commands. 64 | - Add command for generating a new keypair. 65 | - Add command for key/note id conversion between bech32 and hex encodings. 66 | - Add crated badge to readme. 67 | - Add MIT licence. Idc, just use it to whatever you want as long as I'm not liable for it. 68 | 69 | ### Changed 70 | - Refactored codebase to increase readability and isolate different concerns. 71 | - 72 | 73 | ## 0.1.0 74 | - Edit: Upgrade `secp256k1` from `0.24` to `0.25`. 75 | - Add: `update-metadata` command. 76 | - Add: `text-note` command. 77 | - Add: `recommend-server` command. 78 | - Add: `publish-contact-list-csv` command. 79 | - Add: `send-direct-message` command. 80 | - Add: `delete-event` command. 81 | - Add: `react` command. 82 | - Add: `list-events` command. 83 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.22.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "aead" 22 | version = "0.5.2" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" 25 | dependencies = [ 26 | "crypto-common", 27 | "generic-array", 28 | ] 29 | 30 | [[package]] 31 | name = "aes" 32 | version = "0.8.4" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" 35 | dependencies = [ 36 | "cfg-if", 37 | "cipher", 38 | "cpufeatures", 39 | ] 40 | 41 | [[package]] 42 | name = "ahash" 43 | version = "0.8.11" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" 46 | dependencies = [ 47 | "cfg-if", 48 | "once_cell", 49 | "version_check", 50 | "zerocopy", 51 | ] 52 | 53 | [[package]] 54 | name = "allocator-api2" 55 | version = "0.2.18" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 58 | 59 | [[package]] 60 | name = "anstream" 61 | version = "0.6.14" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" 64 | dependencies = [ 65 | "anstyle", 66 | "anstyle-parse", 67 | "anstyle-query", 68 | "anstyle-wincon", 69 | "colorchoice", 70 | "is_terminal_polyfill", 71 | "utf8parse", 72 | ] 73 | 74 | [[package]] 75 | name = "anstyle" 76 | version = "1.0.7" 77 | source = "registry+https://github.com/rust-lang/crates.io-index" 78 | checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" 79 | 80 | [[package]] 81 | name = "anstyle-parse" 82 | version = "0.2.4" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" 85 | dependencies = [ 86 | "utf8parse", 87 | ] 88 | 89 | [[package]] 90 | name = "anstyle-query" 91 | version = "1.1.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" 94 | dependencies = [ 95 | "windows-sys 0.52.0", 96 | ] 97 | 98 | [[package]] 99 | name = "anstyle-wincon" 100 | version = "3.0.3" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" 103 | dependencies = [ 104 | "anstyle", 105 | "windows-sys 0.52.0", 106 | ] 107 | 108 | [[package]] 109 | name = "async-trait" 110 | version = "0.1.80" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" 113 | dependencies = [ 114 | "proc-macro2", 115 | "quote", 116 | "syn", 117 | ] 118 | 119 | [[package]] 120 | name = "async-utility" 121 | version = "0.2.0" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "a349201d80b4aa18d17a34a182bdd7f8ddf845e9e57d2ea130a12e10ef1e3a47" 124 | dependencies = [ 125 | "futures-util", 126 | "gloo-timers", 127 | "tokio", 128 | "wasm-bindgen-futures", 129 | ] 130 | 131 | [[package]] 132 | name = "async-wsocket" 133 | version = "0.5.1" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "79c6465dab65a363da7353383af13a22fb05ce173d9b460c38322590e9245400" 136 | dependencies = [ 137 | "async-utility", 138 | "futures-util", 139 | "thiserror", 140 | "tokio", 141 | "tokio-rustls 0.26.0", 142 | "tokio-socks", 143 | "tokio-tungstenite", 144 | "url", 145 | "wasm-ws", 146 | "webpki-roots", 147 | ] 148 | 149 | [[package]] 150 | name = "async_io_stream" 151 | version = "0.3.3" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" 154 | dependencies = [ 155 | "futures", 156 | "pharos", 157 | "rustc_version", 158 | ] 159 | 160 | [[package]] 161 | name = "atomic-destructor" 162 | version = "0.2.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "7d919cb60ba95c87ba42777e9e246c4e8d658057299b437b7512531ce0a09a23" 165 | dependencies = [ 166 | "tracing", 167 | ] 168 | 169 | [[package]] 170 | name = "autocfg" 171 | version = "1.3.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" 174 | 175 | [[package]] 176 | name = "backtrace" 177 | version = "0.3.72" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" 180 | dependencies = [ 181 | "addr2line", 182 | "cc", 183 | "cfg-if", 184 | "libc", 185 | "miniz_oxide", 186 | "object", 187 | "rustc-demangle", 188 | ] 189 | 190 | [[package]] 191 | name = "base64" 192 | version = "0.21.7" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 195 | 196 | [[package]] 197 | name = "base64" 198 | version = "0.22.1" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 201 | 202 | [[package]] 203 | name = "base64ct" 204 | version = "1.6.0" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 207 | 208 | [[package]] 209 | name = "bech32" 210 | version = "0.10.0-beta" 211 | source = "registry+https://github.com/rust-lang/crates.io-index" 212 | checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea" 213 | 214 | [[package]] 215 | name = "bip39" 216 | version = "2.0.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f" 219 | dependencies = [ 220 | "bitcoin_hashes 0.11.0", 221 | "serde", 222 | "unicode-normalization", 223 | ] 224 | 225 | [[package]] 226 | name = "bitcoin" 227 | version = "0.31.2" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "6c85783c2fe40083ea54a33aa2f0ba58831d90fcd190f5bdc47e74e84d2a96ae" 230 | dependencies = [ 231 | "bech32", 232 | "bitcoin-internals", 233 | "bitcoin_hashes 0.13.0", 234 | "hex-conservative", 235 | "hex_lit", 236 | "secp256k1", 237 | "serde", 238 | ] 239 | 240 | [[package]] 241 | name = "bitcoin-internals" 242 | version = "0.2.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" 245 | dependencies = [ 246 | "serde", 247 | ] 248 | 249 | [[package]] 250 | name = "bitcoin_hashes" 251 | version = "0.11.0" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" 254 | 255 | [[package]] 256 | name = "bitcoin_hashes" 257 | version = "0.13.0" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" 260 | dependencies = [ 261 | "bitcoin-internals", 262 | "hex-conservative", 263 | "serde", 264 | ] 265 | 266 | [[package]] 267 | name = "bitflags" 268 | version = "2.5.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" 271 | 272 | [[package]] 273 | name = "block-buffer" 274 | version = "0.10.4" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 277 | dependencies = [ 278 | "generic-array", 279 | ] 280 | 281 | [[package]] 282 | name = "block-padding" 283 | version = "0.3.3" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" 286 | dependencies = [ 287 | "generic-array", 288 | ] 289 | 290 | [[package]] 291 | name = "bumpalo" 292 | version = "3.16.0" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 295 | 296 | [[package]] 297 | name = "byteorder" 298 | version = "1.5.0" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 301 | 302 | [[package]] 303 | name = "bytes" 304 | version = "1.6.0" 305 | source = "registry+https://github.com/rust-lang/crates.io-index" 306 | checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" 307 | 308 | [[package]] 309 | name = "cbc" 310 | version = "0.1.2" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" 313 | dependencies = [ 314 | "cipher", 315 | ] 316 | 317 | [[package]] 318 | name = "cc" 319 | version = "1.0.99" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" 322 | 323 | [[package]] 324 | name = "cfg-if" 325 | version = "1.0.0" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 328 | 329 | [[package]] 330 | name = "chacha20" 331 | version = "0.9.1" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" 334 | dependencies = [ 335 | "cfg-if", 336 | "cipher", 337 | "cpufeatures", 338 | ] 339 | 340 | [[package]] 341 | name = "chacha20poly1305" 342 | version = "0.10.1" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" 345 | dependencies = [ 346 | "aead", 347 | "chacha20", 348 | "cipher", 349 | "poly1305", 350 | "zeroize", 351 | ] 352 | 353 | [[package]] 354 | name = "cipher" 355 | version = "0.4.4" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" 358 | dependencies = [ 359 | "crypto-common", 360 | "inout", 361 | "zeroize", 362 | ] 363 | 364 | [[package]] 365 | name = "clap" 366 | version = "4.5.7" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" 369 | dependencies = [ 370 | "clap_builder", 371 | "clap_derive", 372 | ] 373 | 374 | [[package]] 375 | name = "clap_builder" 376 | version = "4.5.7" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" 379 | dependencies = [ 380 | "anstream", 381 | "anstyle", 382 | "clap_lex", 383 | "strsim", 384 | ] 385 | 386 | [[package]] 387 | name = "clap_derive" 388 | version = "4.5.5" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" 391 | dependencies = [ 392 | "heck", 393 | "proc-macro2", 394 | "quote", 395 | "syn", 396 | ] 397 | 398 | [[package]] 399 | name = "clap_lex" 400 | version = "0.7.1" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" 403 | 404 | [[package]] 405 | name = "colorchoice" 406 | version = "1.0.1" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" 409 | 410 | [[package]] 411 | name = "cpufeatures" 412 | version = "0.2.12" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" 415 | dependencies = [ 416 | "libc", 417 | ] 418 | 419 | [[package]] 420 | name = "crypto-common" 421 | version = "0.1.6" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 424 | dependencies = [ 425 | "generic-array", 426 | "rand_core", 427 | "typenum", 428 | ] 429 | 430 | [[package]] 431 | name = "csv" 432 | version = "1.3.0" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" 435 | dependencies = [ 436 | "csv-core", 437 | "itoa", 438 | "ryu", 439 | "serde", 440 | ] 441 | 442 | [[package]] 443 | name = "csv-core" 444 | version = "0.1.11" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" 447 | dependencies = [ 448 | "memchr", 449 | ] 450 | 451 | [[package]] 452 | name = "data-encoding" 453 | version = "2.6.0" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" 456 | 457 | [[package]] 458 | name = "digest" 459 | version = "0.10.7" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 462 | dependencies = [ 463 | "block-buffer", 464 | "crypto-common", 465 | "subtle", 466 | ] 467 | 468 | [[package]] 469 | name = "displaydoc" 470 | version = "0.2.4" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" 473 | dependencies = [ 474 | "proc-macro2", 475 | "quote", 476 | "syn", 477 | ] 478 | 479 | [[package]] 480 | name = "either" 481 | version = "1.12.0" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" 484 | 485 | [[package]] 486 | name = "equivalent" 487 | version = "1.0.1" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 490 | 491 | [[package]] 492 | name = "fnv" 493 | version = "1.0.7" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 496 | 497 | [[package]] 498 | name = "form_urlencoded" 499 | version = "1.2.1" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 502 | dependencies = [ 503 | "percent-encoding", 504 | ] 505 | 506 | [[package]] 507 | name = "futures" 508 | version = "0.3.30" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" 511 | dependencies = [ 512 | "futures-channel", 513 | "futures-core", 514 | "futures-executor", 515 | "futures-io", 516 | "futures-sink", 517 | "futures-task", 518 | "futures-util", 519 | ] 520 | 521 | [[package]] 522 | name = "futures-channel" 523 | version = "0.3.30" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 526 | dependencies = [ 527 | "futures-core", 528 | "futures-sink", 529 | ] 530 | 531 | [[package]] 532 | name = "futures-core" 533 | version = "0.3.30" 534 | source = "registry+https://github.com/rust-lang/crates.io-index" 535 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 536 | 537 | [[package]] 538 | name = "futures-executor" 539 | version = "0.3.30" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 542 | dependencies = [ 543 | "futures-core", 544 | "futures-task", 545 | "futures-util", 546 | ] 547 | 548 | [[package]] 549 | name = "futures-io" 550 | version = "0.3.30" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" 553 | 554 | [[package]] 555 | name = "futures-macro" 556 | version = "0.3.30" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 559 | dependencies = [ 560 | "proc-macro2", 561 | "quote", 562 | "syn", 563 | ] 564 | 565 | [[package]] 566 | name = "futures-sink" 567 | version = "0.3.30" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 570 | 571 | [[package]] 572 | name = "futures-task" 573 | version = "0.3.30" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 576 | 577 | [[package]] 578 | name = "futures-util" 579 | version = "0.3.30" 580 | source = "registry+https://github.com/rust-lang/crates.io-index" 581 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 582 | dependencies = [ 583 | "futures-channel", 584 | "futures-core", 585 | "futures-io", 586 | "futures-macro", 587 | "futures-sink", 588 | "futures-task", 589 | "memchr", 590 | "pin-project-lite", 591 | "pin-utils", 592 | "slab", 593 | ] 594 | 595 | [[package]] 596 | name = "generic-array" 597 | version = "0.14.7" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 600 | dependencies = [ 601 | "typenum", 602 | "version_check", 603 | ] 604 | 605 | [[package]] 606 | name = "getrandom" 607 | version = "0.2.15" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 610 | dependencies = [ 611 | "cfg-if", 612 | "js-sys", 613 | "libc", 614 | "wasi", 615 | "wasm-bindgen", 616 | ] 617 | 618 | [[package]] 619 | name = "gimli" 620 | version = "0.29.0" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" 623 | 624 | [[package]] 625 | name = "gloo-timers" 626 | version = "0.2.6" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" 629 | dependencies = [ 630 | "futures-channel", 631 | "futures-core", 632 | "js-sys", 633 | "wasm-bindgen", 634 | ] 635 | 636 | [[package]] 637 | name = "hashbrown" 638 | version = "0.14.5" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 641 | dependencies = [ 642 | "ahash", 643 | "allocator-api2", 644 | ] 645 | 646 | [[package]] 647 | name = "heck" 648 | version = "0.5.0" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 651 | 652 | [[package]] 653 | name = "hermit-abi" 654 | version = "0.3.9" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 657 | 658 | [[package]] 659 | name = "hex-conservative" 660 | version = "0.1.2" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" 663 | 664 | [[package]] 665 | name = "hex_lit" 666 | version = "0.1.1" 667 | source = "registry+https://github.com/rust-lang/crates.io-index" 668 | checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" 669 | 670 | [[package]] 671 | name = "hmac" 672 | version = "0.12.1" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 675 | dependencies = [ 676 | "digest", 677 | ] 678 | 679 | [[package]] 680 | name = "http" 681 | version = "1.1.0" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 684 | dependencies = [ 685 | "bytes", 686 | "fnv", 687 | "itoa", 688 | ] 689 | 690 | [[package]] 691 | name = "http-body" 692 | version = "1.0.0" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" 695 | dependencies = [ 696 | "bytes", 697 | "http", 698 | ] 699 | 700 | [[package]] 701 | name = "http-body-util" 702 | version = "0.1.1" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" 705 | dependencies = [ 706 | "bytes", 707 | "futures-core", 708 | "http", 709 | "http-body", 710 | "pin-project-lite", 711 | ] 712 | 713 | [[package]] 714 | name = "httparse" 715 | version = "1.9.1" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "8720bf4c5bfb5b6c350840c4cd14b787bf00ed51c148c857fbf7a6ddb7062764" 718 | 719 | [[package]] 720 | name = "hyper" 721 | version = "1.3.1" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" 724 | dependencies = [ 725 | "bytes", 726 | "futures-channel", 727 | "futures-util", 728 | "http", 729 | "http-body", 730 | "httparse", 731 | "itoa", 732 | "pin-project-lite", 733 | "smallvec", 734 | "tokio", 735 | "want", 736 | ] 737 | 738 | [[package]] 739 | name = "hyper-rustls" 740 | version = "0.26.0" 741 | source = "registry+https://github.com/rust-lang/crates.io-index" 742 | checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" 743 | dependencies = [ 744 | "futures-util", 745 | "http", 746 | "hyper", 747 | "hyper-util", 748 | "rustls 0.22.4", 749 | "rustls-pki-types", 750 | "tokio", 751 | "tokio-rustls 0.25.0", 752 | "tower-service", 753 | ] 754 | 755 | [[package]] 756 | name = "hyper-util" 757 | version = "0.1.5" 758 | source = "registry+https://github.com/rust-lang/crates.io-index" 759 | checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" 760 | dependencies = [ 761 | "bytes", 762 | "futures-channel", 763 | "futures-util", 764 | "http", 765 | "http-body", 766 | "hyper", 767 | "pin-project-lite", 768 | "socket2", 769 | "tokio", 770 | "tower", 771 | "tower-service", 772 | "tracing", 773 | ] 774 | 775 | [[package]] 776 | name = "icu_collections" 777 | version = "1.5.0" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 780 | dependencies = [ 781 | "displaydoc", 782 | "yoke", 783 | "zerofrom", 784 | "zerovec", 785 | ] 786 | 787 | [[package]] 788 | name = "icu_locid" 789 | version = "1.5.0" 790 | source = "registry+https://github.com/rust-lang/crates.io-index" 791 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 792 | dependencies = [ 793 | "displaydoc", 794 | "litemap", 795 | "tinystr", 796 | "writeable", 797 | "zerovec", 798 | ] 799 | 800 | [[package]] 801 | name = "icu_locid_transform" 802 | version = "1.5.0" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 805 | dependencies = [ 806 | "displaydoc", 807 | "icu_locid", 808 | "icu_locid_transform_data", 809 | "icu_provider", 810 | "tinystr", 811 | "zerovec", 812 | ] 813 | 814 | [[package]] 815 | name = "icu_locid_transform_data" 816 | version = "1.5.0" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 819 | 820 | [[package]] 821 | name = "icu_normalizer" 822 | version = "1.5.0" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 825 | dependencies = [ 826 | "displaydoc", 827 | "icu_collections", 828 | "icu_normalizer_data", 829 | "icu_properties", 830 | "icu_provider", 831 | "smallvec", 832 | "utf16_iter", 833 | "utf8_iter", 834 | "write16", 835 | "zerovec", 836 | ] 837 | 838 | [[package]] 839 | name = "icu_normalizer_data" 840 | version = "1.5.0" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 843 | 844 | [[package]] 845 | name = "icu_properties" 846 | version = "1.5.0" 847 | source = "registry+https://github.com/rust-lang/crates.io-index" 848 | checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" 849 | dependencies = [ 850 | "displaydoc", 851 | "icu_collections", 852 | "icu_locid_transform", 853 | "icu_properties_data", 854 | "icu_provider", 855 | "tinystr", 856 | "zerovec", 857 | ] 858 | 859 | [[package]] 860 | name = "icu_properties_data" 861 | version = "1.5.0" 862 | source = "registry+https://github.com/rust-lang/crates.io-index" 863 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 864 | 865 | [[package]] 866 | name = "icu_provider" 867 | version = "1.5.0" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 870 | dependencies = [ 871 | "displaydoc", 872 | "icu_locid", 873 | "icu_provider_macros", 874 | "stable_deref_trait", 875 | "tinystr", 876 | "writeable", 877 | "yoke", 878 | "zerofrom", 879 | "zerovec", 880 | ] 881 | 882 | [[package]] 883 | name = "icu_provider_macros" 884 | version = "1.5.0" 885 | source = "registry+https://github.com/rust-lang/crates.io-index" 886 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 887 | dependencies = [ 888 | "proc-macro2", 889 | "quote", 890 | "syn", 891 | ] 892 | 893 | [[package]] 894 | name = "idna" 895 | version = "1.0.0" 896 | source = "registry+https://github.com/rust-lang/crates.io-index" 897 | checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" 898 | dependencies = [ 899 | "icu_normalizer", 900 | "icu_properties", 901 | "smallvec", 902 | "utf8_iter", 903 | ] 904 | 905 | [[package]] 906 | name = "indexmap" 907 | version = "2.2.6" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" 910 | dependencies = [ 911 | "equivalent", 912 | "hashbrown", 913 | ] 914 | 915 | [[package]] 916 | name = "inout" 917 | version = "0.1.3" 918 | source = "registry+https://github.com/rust-lang/crates.io-index" 919 | checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" 920 | dependencies = [ 921 | "block-padding", 922 | "generic-array", 923 | ] 924 | 925 | [[package]] 926 | name = "instant" 927 | version = "0.1.13" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" 930 | dependencies = [ 931 | "cfg-if", 932 | "js-sys", 933 | "wasm-bindgen", 934 | "web-sys", 935 | ] 936 | 937 | [[package]] 938 | name = "ipnet" 939 | version = "2.9.0" 940 | source = "registry+https://github.com/rust-lang/crates.io-index" 941 | checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" 942 | 943 | [[package]] 944 | name = "is_terminal_polyfill" 945 | version = "1.70.0" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" 948 | 949 | [[package]] 950 | name = "itoa" 951 | version = "1.0.11" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 954 | 955 | [[package]] 956 | name = "js-sys" 957 | version = "0.3.69" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" 960 | dependencies = [ 961 | "wasm-bindgen", 962 | ] 963 | 964 | [[package]] 965 | name = "libc" 966 | version = "0.2.155" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" 969 | 970 | [[package]] 971 | name = "litemap" 972 | version = "0.7.3" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" 975 | 976 | [[package]] 977 | name = "lnurl-pay" 978 | version = "0.5.0" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "02c042191c2e3f27147decfad8182eea2c7dd1c6c1733562e25d3d401369669d" 981 | dependencies = [ 982 | "bech32", 983 | "reqwest", 984 | "serde", 985 | "serde_json", 986 | ] 987 | 988 | [[package]] 989 | name = "lock_api" 990 | version = "0.4.12" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 993 | dependencies = [ 994 | "autocfg", 995 | "scopeguard", 996 | ] 997 | 998 | [[package]] 999 | name = "log" 1000 | version = "0.4.21" 1001 | source = "registry+https://github.com/rust-lang/crates.io-index" 1002 | checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" 1003 | 1004 | [[package]] 1005 | name = "lru" 1006 | version = "0.12.3" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" 1009 | dependencies = [ 1010 | "hashbrown", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "memchr" 1015 | version = "2.7.2" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" 1018 | 1019 | [[package]] 1020 | name = "mime" 1021 | version = "0.3.17" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 1024 | 1025 | [[package]] 1026 | name = "miniz_oxide" 1027 | version = "0.7.3" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" 1030 | dependencies = [ 1031 | "adler", 1032 | ] 1033 | 1034 | [[package]] 1035 | name = "mio" 1036 | version = "0.8.11" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 1039 | dependencies = [ 1040 | "libc", 1041 | "wasi", 1042 | "windows-sys 0.48.0", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "negentropy" 1047 | version = "0.3.1" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "e664971378a3987224f7a0e10059782035e89899ae403718ee07de85bec42afe" 1050 | 1051 | [[package]] 1052 | name = "nostr" 1053 | version = "0.32.0" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "1534cec170e72b57b82323422ac243716d9817d13ed77f3b952eb5133ebef060" 1056 | dependencies = [ 1057 | "aes", 1058 | "base64 0.21.7", 1059 | "bip39", 1060 | "bitcoin", 1061 | "cbc", 1062 | "chacha20", 1063 | "chacha20poly1305", 1064 | "getrandom", 1065 | "instant", 1066 | "js-sys", 1067 | "negentropy", 1068 | "once_cell", 1069 | "reqwest", 1070 | "scrypt", 1071 | "serde", 1072 | "serde_json", 1073 | "tracing", 1074 | "unicode-normalization", 1075 | "url", 1076 | "wasm-bindgen", 1077 | "wasm-bindgen-futures", 1078 | "web-sys", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "nostr-database" 1083 | version = "0.32.0" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "a88a72f92fbd5d2514db36e07a864646f1c1f44931c4a5ea195f6961029af4b3" 1086 | dependencies = [ 1087 | "async-trait", 1088 | "lru", 1089 | "nostr", 1090 | "thiserror", 1091 | "tokio", 1092 | "tracing", 1093 | ] 1094 | 1095 | [[package]] 1096 | name = "nostr-relay-pool" 1097 | version = "0.32.0" 1098 | source = "registry+https://github.com/rust-lang/crates.io-index" 1099 | checksum = "d7b7bf72b02a24ccc7cf87033fa5ddfd57001c7d8c2e757321a7ca7a6df39876" 1100 | dependencies = [ 1101 | "async-utility", 1102 | "async-wsocket", 1103 | "atomic-destructor", 1104 | "nostr", 1105 | "nostr-database", 1106 | "thiserror", 1107 | "tokio", 1108 | "tracing", 1109 | ] 1110 | 1111 | [[package]] 1112 | name = "nostr-sdk" 1113 | version = "0.32.0" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "005915a59ee6401f23ba510c3a9ac4a07b457f80dfe1dc05cd2c8fdbde439246" 1116 | dependencies = [ 1117 | "async-utility", 1118 | "atomic-destructor", 1119 | "lnurl-pay", 1120 | "nostr", 1121 | "nostr-database", 1122 | "nostr-relay-pool", 1123 | "nostr-signer", 1124 | "nostr-zapper", 1125 | "nwc", 1126 | "thiserror", 1127 | "tokio", 1128 | "tracing", 1129 | ] 1130 | 1131 | [[package]] 1132 | name = "nostr-signer" 1133 | version = "0.32.0" 1134 | source = "registry+https://github.com/rust-lang/crates.io-index" 1135 | checksum = "525574dc32fa07d64d04a6c72b534d97455b1ad954c29753c820c834c94e3704" 1136 | dependencies = [ 1137 | "async-utility", 1138 | "nostr", 1139 | "nostr-relay-pool", 1140 | "thiserror", 1141 | "tokio", 1142 | "tracing", 1143 | ] 1144 | 1145 | [[package]] 1146 | name = "nostr-tool" 1147 | version = "0.5.1" 1148 | dependencies = [ 1149 | "clap", 1150 | "csv", 1151 | "nostr-sdk", 1152 | "num_cpus", 1153 | "serde", 1154 | "serde_json", 1155 | "tokio", 1156 | "url", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "nostr-zapper" 1161 | version = "0.32.0" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "430c2527c0efd2e7f1a421b0c7df01a03b334a79c60c39cc7a1ca684f720f2bf" 1164 | dependencies = [ 1165 | "async-trait", 1166 | "nostr", 1167 | "thiserror", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "num_cpus" 1172 | version = "1.16.0" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 1175 | dependencies = [ 1176 | "hermit-abi", 1177 | "libc", 1178 | ] 1179 | 1180 | [[package]] 1181 | name = "nwc" 1182 | version = "0.32.0" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "c6fb91e4be3f6b872fc23c7714bbb500a58a1d59f458eb6eb9dd249fbec42fc2" 1185 | dependencies = [ 1186 | "async-utility", 1187 | "nostr", 1188 | "nostr-relay-pool", 1189 | "nostr-zapper", 1190 | "thiserror", 1191 | "tracing", 1192 | ] 1193 | 1194 | [[package]] 1195 | name = "object" 1196 | version = "0.35.0" 1197 | source = "registry+https://github.com/rust-lang/crates.io-index" 1198 | checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" 1199 | dependencies = [ 1200 | "memchr", 1201 | ] 1202 | 1203 | [[package]] 1204 | name = "once_cell" 1205 | version = "1.19.0" 1206 | source = "registry+https://github.com/rust-lang/crates.io-index" 1207 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 1208 | 1209 | [[package]] 1210 | name = "opaque-debug" 1211 | version = "0.3.1" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" 1214 | 1215 | [[package]] 1216 | name = "parking_lot" 1217 | version = "0.12.3" 1218 | source = "registry+https://github.com/rust-lang/crates.io-index" 1219 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 1220 | dependencies = [ 1221 | "lock_api", 1222 | "parking_lot_core", 1223 | ] 1224 | 1225 | [[package]] 1226 | name = "parking_lot_core" 1227 | version = "0.9.10" 1228 | source = "registry+https://github.com/rust-lang/crates.io-index" 1229 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 1230 | dependencies = [ 1231 | "cfg-if", 1232 | "libc", 1233 | "redox_syscall", 1234 | "smallvec", 1235 | "windows-targets 0.52.5", 1236 | ] 1237 | 1238 | [[package]] 1239 | name = "password-hash" 1240 | version = "0.5.0" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" 1243 | dependencies = [ 1244 | "base64ct", 1245 | "rand_core", 1246 | "subtle", 1247 | ] 1248 | 1249 | [[package]] 1250 | name = "pbkdf2" 1251 | version = "0.12.2" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" 1254 | dependencies = [ 1255 | "digest", 1256 | "hmac", 1257 | ] 1258 | 1259 | [[package]] 1260 | name = "percent-encoding" 1261 | version = "2.3.1" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 1264 | 1265 | [[package]] 1266 | name = "pharos" 1267 | version = "0.5.3" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" 1270 | dependencies = [ 1271 | "futures", 1272 | "rustc_version", 1273 | ] 1274 | 1275 | [[package]] 1276 | name = "pin-project" 1277 | version = "1.1.5" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 1280 | dependencies = [ 1281 | "pin-project-internal", 1282 | ] 1283 | 1284 | [[package]] 1285 | name = "pin-project-internal" 1286 | version = "1.1.5" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 1289 | dependencies = [ 1290 | "proc-macro2", 1291 | "quote", 1292 | "syn", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "pin-project-lite" 1297 | version = "0.2.14" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 1300 | 1301 | [[package]] 1302 | name = "pin-utils" 1303 | version = "0.1.0" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1306 | 1307 | [[package]] 1308 | name = "poly1305" 1309 | version = "0.8.0" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" 1312 | dependencies = [ 1313 | "cpufeatures", 1314 | "opaque-debug", 1315 | "universal-hash", 1316 | ] 1317 | 1318 | [[package]] 1319 | name = "ppv-lite86" 1320 | version = "0.2.17" 1321 | source = "registry+https://github.com/rust-lang/crates.io-index" 1322 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1323 | 1324 | [[package]] 1325 | name = "proc-macro2" 1326 | version = "1.0.85" 1327 | source = "registry+https://github.com/rust-lang/crates.io-index" 1328 | checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" 1329 | dependencies = [ 1330 | "unicode-ident", 1331 | ] 1332 | 1333 | [[package]] 1334 | name = "quote" 1335 | version = "1.0.36" 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" 1337 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 1338 | dependencies = [ 1339 | "proc-macro2", 1340 | ] 1341 | 1342 | [[package]] 1343 | name = "rand" 1344 | version = "0.8.5" 1345 | source = "registry+https://github.com/rust-lang/crates.io-index" 1346 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1347 | dependencies = [ 1348 | "libc", 1349 | "rand_chacha", 1350 | "rand_core", 1351 | ] 1352 | 1353 | [[package]] 1354 | name = "rand_chacha" 1355 | version = "0.3.1" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1358 | dependencies = [ 1359 | "ppv-lite86", 1360 | "rand_core", 1361 | ] 1362 | 1363 | [[package]] 1364 | name = "rand_core" 1365 | version = "0.6.4" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1368 | dependencies = [ 1369 | "getrandom", 1370 | ] 1371 | 1372 | [[package]] 1373 | name = "redox_syscall" 1374 | version = "0.5.1" 1375 | source = "registry+https://github.com/rust-lang/crates.io-index" 1376 | checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" 1377 | dependencies = [ 1378 | "bitflags", 1379 | ] 1380 | 1381 | [[package]] 1382 | name = "reqwest" 1383 | version = "0.12.4" 1384 | source = "registry+https://github.com/rust-lang/crates.io-index" 1385 | checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" 1386 | dependencies = [ 1387 | "base64 0.22.1", 1388 | "bytes", 1389 | "futures-core", 1390 | "futures-util", 1391 | "http", 1392 | "http-body", 1393 | "http-body-util", 1394 | "hyper", 1395 | "hyper-rustls", 1396 | "hyper-util", 1397 | "ipnet", 1398 | "js-sys", 1399 | "log", 1400 | "mime", 1401 | "once_cell", 1402 | "percent-encoding", 1403 | "pin-project-lite", 1404 | "rustls 0.22.4", 1405 | "rustls-pemfile", 1406 | "rustls-pki-types", 1407 | "serde", 1408 | "serde_json", 1409 | "serde_urlencoded", 1410 | "sync_wrapper", 1411 | "tokio", 1412 | "tokio-rustls 0.25.0", 1413 | "tokio-socks", 1414 | "tower-service", 1415 | "url", 1416 | "wasm-bindgen", 1417 | "wasm-bindgen-futures", 1418 | "web-sys", 1419 | "webpki-roots", 1420 | "winreg", 1421 | ] 1422 | 1423 | [[package]] 1424 | name = "ring" 1425 | version = "0.17.8" 1426 | source = "registry+https://github.com/rust-lang/crates.io-index" 1427 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 1428 | dependencies = [ 1429 | "cc", 1430 | "cfg-if", 1431 | "getrandom", 1432 | "libc", 1433 | "spin", 1434 | "untrusted", 1435 | "windows-sys 0.52.0", 1436 | ] 1437 | 1438 | [[package]] 1439 | name = "rustc-demangle" 1440 | version = "0.1.24" 1441 | source = "registry+https://github.com/rust-lang/crates.io-index" 1442 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1443 | 1444 | [[package]] 1445 | name = "rustc_version" 1446 | version = "0.4.0" 1447 | source = "registry+https://github.com/rust-lang/crates.io-index" 1448 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1449 | dependencies = [ 1450 | "semver", 1451 | ] 1452 | 1453 | [[package]] 1454 | name = "rustls" 1455 | version = "0.22.4" 1456 | source = "registry+https://github.com/rust-lang/crates.io-index" 1457 | checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" 1458 | dependencies = [ 1459 | "log", 1460 | "ring", 1461 | "rustls-pki-types", 1462 | "rustls-webpki", 1463 | "subtle", 1464 | "zeroize", 1465 | ] 1466 | 1467 | [[package]] 1468 | name = "rustls" 1469 | version = "0.23.9" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "a218f0f6d05669de4eabfb24f31ce802035c952429d037507b4a4a39f0e60c5b" 1472 | dependencies = [ 1473 | "once_cell", 1474 | "ring", 1475 | "rustls-pki-types", 1476 | "rustls-webpki", 1477 | "subtle", 1478 | "zeroize", 1479 | ] 1480 | 1481 | [[package]] 1482 | name = "rustls-pemfile" 1483 | version = "2.1.2" 1484 | source = "registry+https://github.com/rust-lang/crates.io-index" 1485 | checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" 1486 | dependencies = [ 1487 | "base64 0.22.1", 1488 | "rustls-pki-types", 1489 | ] 1490 | 1491 | [[package]] 1492 | name = "rustls-pki-types" 1493 | version = "1.7.0" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" 1496 | 1497 | [[package]] 1498 | name = "rustls-webpki" 1499 | version = "0.102.4" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" 1502 | dependencies = [ 1503 | "ring", 1504 | "rustls-pki-types", 1505 | "untrusted", 1506 | ] 1507 | 1508 | [[package]] 1509 | name = "ryu" 1510 | version = "1.0.18" 1511 | source = "registry+https://github.com/rust-lang/crates.io-index" 1512 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1513 | 1514 | [[package]] 1515 | name = "salsa20" 1516 | version = "0.10.2" 1517 | source = "registry+https://github.com/rust-lang/crates.io-index" 1518 | checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213" 1519 | dependencies = [ 1520 | "cipher", 1521 | ] 1522 | 1523 | [[package]] 1524 | name = "scopeguard" 1525 | version = "1.2.0" 1526 | source = "registry+https://github.com/rust-lang/crates.io-index" 1527 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1528 | 1529 | [[package]] 1530 | name = "scrypt" 1531 | version = "0.11.0" 1532 | source = "registry+https://github.com/rust-lang/crates.io-index" 1533 | checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f" 1534 | dependencies = [ 1535 | "password-hash", 1536 | "pbkdf2", 1537 | "salsa20", 1538 | "sha2", 1539 | ] 1540 | 1541 | [[package]] 1542 | name = "secp256k1" 1543 | version = "0.28.2" 1544 | source = "registry+https://github.com/rust-lang/crates.io-index" 1545 | checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" 1546 | dependencies = [ 1547 | "bitcoin_hashes 0.13.0", 1548 | "rand", 1549 | "secp256k1-sys", 1550 | "serde", 1551 | ] 1552 | 1553 | [[package]] 1554 | name = "secp256k1-sys" 1555 | version = "0.9.2" 1556 | source = "registry+https://github.com/rust-lang/crates.io-index" 1557 | checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" 1558 | dependencies = [ 1559 | "cc", 1560 | ] 1561 | 1562 | [[package]] 1563 | name = "semver" 1564 | version = "1.0.23" 1565 | source = "registry+https://github.com/rust-lang/crates.io-index" 1566 | checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" 1567 | 1568 | [[package]] 1569 | name = "send_wrapper" 1570 | version = "0.6.0" 1571 | source = "registry+https://github.com/rust-lang/crates.io-index" 1572 | checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" 1573 | 1574 | [[package]] 1575 | name = "serde" 1576 | version = "1.0.203" 1577 | source = "registry+https://github.com/rust-lang/crates.io-index" 1578 | checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" 1579 | dependencies = [ 1580 | "serde_derive", 1581 | ] 1582 | 1583 | [[package]] 1584 | name = "serde_derive" 1585 | version = "1.0.203" 1586 | source = "registry+https://github.com/rust-lang/crates.io-index" 1587 | checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" 1588 | dependencies = [ 1589 | "proc-macro2", 1590 | "quote", 1591 | "syn", 1592 | ] 1593 | 1594 | [[package]] 1595 | name = "serde_json" 1596 | version = "1.0.117" 1597 | source = "registry+https://github.com/rust-lang/crates.io-index" 1598 | checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" 1599 | dependencies = [ 1600 | "indexmap", 1601 | "itoa", 1602 | "ryu", 1603 | "serde", 1604 | ] 1605 | 1606 | [[package]] 1607 | name = "serde_urlencoded" 1608 | version = "0.7.1" 1609 | source = "registry+https://github.com/rust-lang/crates.io-index" 1610 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1611 | dependencies = [ 1612 | "form_urlencoded", 1613 | "itoa", 1614 | "ryu", 1615 | "serde", 1616 | ] 1617 | 1618 | [[package]] 1619 | name = "sha1" 1620 | version = "0.10.6" 1621 | source = "registry+https://github.com/rust-lang/crates.io-index" 1622 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1623 | dependencies = [ 1624 | "cfg-if", 1625 | "cpufeatures", 1626 | "digest", 1627 | ] 1628 | 1629 | [[package]] 1630 | name = "sha2" 1631 | version = "0.10.8" 1632 | source = "registry+https://github.com/rust-lang/crates.io-index" 1633 | checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" 1634 | dependencies = [ 1635 | "cfg-if", 1636 | "cpufeatures", 1637 | "digest", 1638 | ] 1639 | 1640 | [[package]] 1641 | name = "signal-hook-registry" 1642 | version = "1.4.2" 1643 | source = "registry+https://github.com/rust-lang/crates.io-index" 1644 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1645 | dependencies = [ 1646 | "libc", 1647 | ] 1648 | 1649 | [[package]] 1650 | name = "slab" 1651 | version = "0.4.9" 1652 | source = "registry+https://github.com/rust-lang/crates.io-index" 1653 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1654 | dependencies = [ 1655 | "autocfg", 1656 | ] 1657 | 1658 | [[package]] 1659 | name = "smallvec" 1660 | version = "1.13.2" 1661 | source = "registry+https://github.com/rust-lang/crates.io-index" 1662 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1663 | 1664 | [[package]] 1665 | name = "socket2" 1666 | version = "0.5.7" 1667 | source = "registry+https://github.com/rust-lang/crates.io-index" 1668 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1669 | dependencies = [ 1670 | "libc", 1671 | "windows-sys 0.52.0", 1672 | ] 1673 | 1674 | [[package]] 1675 | name = "spin" 1676 | version = "0.9.8" 1677 | source = "registry+https://github.com/rust-lang/crates.io-index" 1678 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1679 | 1680 | [[package]] 1681 | name = "stable_deref_trait" 1682 | version = "1.2.0" 1683 | source = "registry+https://github.com/rust-lang/crates.io-index" 1684 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1685 | 1686 | [[package]] 1687 | name = "strsim" 1688 | version = "0.11.1" 1689 | source = "registry+https://github.com/rust-lang/crates.io-index" 1690 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 1691 | 1692 | [[package]] 1693 | name = "subtle" 1694 | version = "2.5.0" 1695 | source = "registry+https://github.com/rust-lang/crates.io-index" 1696 | checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" 1697 | 1698 | [[package]] 1699 | name = "syn" 1700 | version = "2.0.66" 1701 | source = "registry+https://github.com/rust-lang/crates.io-index" 1702 | checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" 1703 | dependencies = [ 1704 | "proc-macro2", 1705 | "quote", 1706 | "unicode-ident", 1707 | ] 1708 | 1709 | [[package]] 1710 | name = "sync_wrapper" 1711 | version = "0.1.2" 1712 | source = "registry+https://github.com/rust-lang/crates.io-index" 1713 | checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" 1714 | 1715 | [[package]] 1716 | name = "synstructure" 1717 | version = "0.13.1" 1718 | source = "registry+https://github.com/rust-lang/crates.io-index" 1719 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1720 | dependencies = [ 1721 | "proc-macro2", 1722 | "quote", 1723 | "syn", 1724 | ] 1725 | 1726 | [[package]] 1727 | name = "thiserror" 1728 | version = "1.0.61" 1729 | source = "registry+https://github.com/rust-lang/crates.io-index" 1730 | checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" 1731 | dependencies = [ 1732 | "thiserror-impl", 1733 | ] 1734 | 1735 | [[package]] 1736 | name = "thiserror-impl" 1737 | version = "1.0.61" 1738 | source = "registry+https://github.com/rust-lang/crates.io-index" 1739 | checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" 1740 | dependencies = [ 1741 | "proc-macro2", 1742 | "quote", 1743 | "syn", 1744 | ] 1745 | 1746 | [[package]] 1747 | name = "tinystr" 1748 | version = "0.7.6" 1749 | source = "registry+https://github.com/rust-lang/crates.io-index" 1750 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1751 | dependencies = [ 1752 | "displaydoc", 1753 | "zerovec", 1754 | ] 1755 | 1756 | [[package]] 1757 | name = "tinyvec" 1758 | version = "1.6.0" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1761 | dependencies = [ 1762 | "tinyvec_macros", 1763 | ] 1764 | 1765 | [[package]] 1766 | name = "tinyvec_macros" 1767 | version = "0.1.1" 1768 | source = "registry+https://github.com/rust-lang/crates.io-index" 1769 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1770 | 1771 | [[package]] 1772 | name = "tokio" 1773 | version = "1.38.0" 1774 | source = "registry+https://github.com/rust-lang/crates.io-index" 1775 | checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" 1776 | dependencies = [ 1777 | "backtrace", 1778 | "bytes", 1779 | "libc", 1780 | "mio", 1781 | "num_cpus", 1782 | "parking_lot", 1783 | "pin-project-lite", 1784 | "signal-hook-registry", 1785 | "socket2", 1786 | "tokio-macros", 1787 | "windows-sys 0.48.0", 1788 | ] 1789 | 1790 | [[package]] 1791 | name = "tokio-macros" 1792 | version = "2.3.0" 1793 | source = "registry+https://github.com/rust-lang/crates.io-index" 1794 | checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" 1795 | dependencies = [ 1796 | "proc-macro2", 1797 | "quote", 1798 | "syn", 1799 | ] 1800 | 1801 | [[package]] 1802 | name = "tokio-rustls" 1803 | version = "0.25.0" 1804 | source = "registry+https://github.com/rust-lang/crates.io-index" 1805 | checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" 1806 | dependencies = [ 1807 | "rustls 0.22.4", 1808 | "rustls-pki-types", 1809 | "tokio", 1810 | ] 1811 | 1812 | [[package]] 1813 | name = "tokio-rustls" 1814 | version = "0.26.0" 1815 | source = "registry+https://github.com/rust-lang/crates.io-index" 1816 | checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" 1817 | dependencies = [ 1818 | "rustls 0.23.9", 1819 | "rustls-pki-types", 1820 | "tokio", 1821 | ] 1822 | 1823 | [[package]] 1824 | name = "tokio-socks" 1825 | version = "0.5.1" 1826 | source = "registry+https://github.com/rust-lang/crates.io-index" 1827 | checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0" 1828 | dependencies = [ 1829 | "either", 1830 | "futures-util", 1831 | "thiserror", 1832 | "tokio", 1833 | ] 1834 | 1835 | [[package]] 1836 | name = "tokio-tungstenite" 1837 | version = "0.23.0" 1838 | source = "registry+https://github.com/rust-lang/crates.io-index" 1839 | checksum = "becd34a233e7e31a3dbf7c7241b38320f57393dcae8e7324b0167d21b8e320b0" 1840 | dependencies = [ 1841 | "futures-util", 1842 | "log", 1843 | "rustls 0.23.9", 1844 | "rustls-pki-types", 1845 | "tokio", 1846 | "tokio-rustls 0.26.0", 1847 | "tungstenite", 1848 | "webpki-roots", 1849 | ] 1850 | 1851 | [[package]] 1852 | name = "tower" 1853 | version = "0.4.13" 1854 | source = "registry+https://github.com/rust-lang/crates.io-index" 1855 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 1856 | dependencies = [ 1857 | "futures-core", 1858 | "futures-util", 1859 | "pin-project", 1860 | "pin-project-lite", 1861 | "tokio", 1862 | "tower-layer", 1863 | "tower-service", 1864 | ] 1865 | 1866 | [[package]] 1867 | name = "tower-layer" 1868 | version = "0.3.2" 1869 | source = "registry+https://github.com/rust-lang/crates.io-index" 1870 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 1871 | 1872 | [[package]] 1873 | name = "tower-service" 1874 | version = "0.3.2" 1875 | source = "registry+https://github.com/rust-lang/crates.io-index" 1876 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1877 | 1878 | [[package]] 1879 | name = "tracing" 1880 | version = "0.1.40" 1881 | source = "registry+https://github.com/rust-lang/crates.io-index" 1882 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1883 | dependencies = [ 1884 | "pin-project-lite", 1885 | "tracing-attributes", 1886 | "tracing-core", 1887 | ] 1888 | 1889 | [[package]] 1890 | name = "tracing-attributes" 1891 | version = "0.1.27" 1892 | source = "registry+https://github.com/rust-lang/crates.io-index" 1893 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 1894 | dependencies = [ 1895 | "proc-macro2", 1896 | "quote", 1897 | "syn", 1898 | ] 1899 | 1900 | [[package]] 1901 | name = "tracing-core" 1902 | version = "0.1.32" 1903 | source = "registry+https://github.com/rust-lang/crates.io-index" 1904 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1905 | dependencies = [ 1906 | "once_cell", 1907 | ] 1908 | 1909 | [[package]] 1910 | name = "try-lock" 1911 | version = "0.2.5" 1912 | source = "registry+https://github.com/rust-lang/crates.io-index" 1913 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1914 | 1915 | [[package]] 1916 | name = "tungstenite" 1917 | version = "0.23.0" 1918 | source = "registry+https://github.com/rust-lang/crates.io-index" 1919 | checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" 1920 | dependencies = [ 1921 | "byteorder", 1922 | "bytes", 1923 | "data-encoding", 1924 | "http", 1925 | "httparse", 1926 | "log", 1927 | "rand", 1928 | "rustls 0.23.9", 1929 | "rustls-pki-types", 1930 | "sha1", 1931 | "thiserror", 1932 | "utf-8", 1933 | ] 1934 | 1935 | [[package]] 1936 | name = "typenum" 1937 | version = "1.17.0" 1938 | source = "registry+https://github.com/rust-lang/crates.io-index" 1939 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1940 | 1941 | [[package]] 1942 | name = "unicode-ident" 1943 | version = "1.0.12" 1944 | source = "registry+https://github.com/rust-lang/crates.io-index" 1945 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1946 | 1947 | [[package]] 1948 | name = "unicode-normalization" 1949 | version = "0.1.22" 1950 | source = "registry+https://github.com/rust-lang/crates.io-index" 1951 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1952 | dependencies = [ 1953 | "tinyvec", 1954 | ] 1955 | 1956 | [[package]] 1957 | name = "universal-hash" 1958 | version = "0.5.1" 1959 | source = "registry+https://github.com/rust-lang/crates.io-index" 1960 | checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" 1961 | dependencies = [ 1962 | "crypto-common", 1963 | "subtle", 1964 | ] 1965 | 1966 | [[package]] 1967 | name = "untrusted" 1968 | version = "0.9.0" 1969 | source = "registry+https://github.com/rust-lang/crates.io-index" 1970 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1971 | 1972 | [[package]] 1973 | name = "url" 1974 | version = "2.5.1" 1975 | source = "registry+https://github.com/rust-lang/crates.io-index" 1976 | checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" 1977 | dependencies = [ 1978 | "form_urlencoded", 1979 | "idna", 1980 | "percent-encoding", 1981 | "serde", 1982 | ] 1983 | 1984 | [[package]] 1985 | name = "utf-8" 1986 | version = "0.7.6" 1987 | source = "registry+https://github.com/rust-lang/crates.io-index" 1988 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1989 | 1990 | [[package]] 1991 | name = "utf16_iter" 1992 | version = "1.0.5" 1993 | source = "registry+https://github.com/rust-lang/crates.io-index" 1994 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 1995 | 1996 | [[package]] 1997 | name = "utf8_iter" 1998 | version = "1.0.4" 1999 | source = "registry+https://github.com/rust-lang/crates.io-index" 2000 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 2001 | 2002 | [[package]] 2003 | name = "utf8parse" 2004 | version = "0.2.2" 2005 | source = "registry+https://github.com/rust-lang/crates.io-index" 2006 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 2007 | 2008 | [[package]] 2009 | name = "version_check" 2010 | version = "0.9.4" 2011 | source = "registry+https://github.com/rust-lang/crates.io-index" 2012 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 2013 | 2014 | [[package]] 2015 | name = "want" 2016 | version = "0.3.1" 2017 | source = "registry+https://github.com/rust-lang/crates.io-index" 2018 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 2019 | dependencies = [ 2020 | "try-lock", 2021 | ] 2022 | 2023 | [[package]] 2024 | name = "wasi" 2025 | version = "0.11.0+wasi-snapshot-preview1" 2026 | source = "registry+https://github.com/rust-lang/crates.io-index" 2027 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2028 | 2029 | [[package]] 2030 | name = "wasm-bindgen" 2031 | version = "0.2.92" 2032 | source = "registry+https://github.com/rust-lang/crates.io-index" 2033 | checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" 2034 | dependencies = [ 2035 | "cfg-if", 2036 | "wasm-bindgen-macro", 2037 | ] 2038 | 2039 | [[package]] 2040 | name = "wasm-bindgen-backend" 2041 | version = "0.2.92" 2042 | source = "registry+https://github.com/rust-lang/crates.io-index" 2043 | checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" 2044 | dependencies = [ 2045 | "bumpalo", 2046 | "log", 2047 | "once_cell", 2048 | "proc-macro2", 2049 | "quote", 2050 | "syn", 2051 | "wasm-bindgen-shared", 2052 | ] 2053 | 2054 | [[package]] 2055 | name = "wasm-bindgen-futures" 2056 | version = "0.4.42" 2057 | source = "registry+https://github.com/rust-lang/crates.io-index" 2058 | checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" 2059 | dependencies = [ 2060 | "cfg-if", 2061 | "js-sys", 2062 | "wasm-bindgen", 2063 | "web-sys", 2064 | ] 2065 | 2066 | [[package]] 2067 | name = "wasm-bindgen-macro" 2068 | version = "0.2.92" 2069 | source = "registry+https://github.com/rust-lang/crates.io-index" 2070 | checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" 2071 | dependencies = [ 2072 | "quote", 2073 | "wasm-bindgen-macro-support", 2074 | ] 2075 | 2076 | [[package]] 2077 | name = "wasm-bindgen-macro-support" 2078 | version = "0.2.92" 2079 | source = "registry+https://github.com/rust-lang/crates.io-index" 2080 | checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" 2081 | dependencies = [ 2082 | "proc-macro2", 2083 | "quote", 2084 | "syn", 2085 | "wasm-bindgen-backend", 2086 | "wasm-bindgen-shared", 2087 | ] 2088 | 2089 | [[package]] 2090 | name = "wasm-bindgen-shared" 2091 | version = "0.2.92" 2092 | source = "registry+https://github.com/rust-lang/crates.io-index" 2093 | checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" 2094 | 2095 | [[package]] 2096 | name = "wasm-ws" 2097 | version = "0.2.1" 2098 | source = "registry+https://github.com/rust-lang/crates.io-index" 2099 | checksum = "688c5806d1b06b4f3d90d015e23364dc5d3af412ee64abba6dde8fdc01637e33" 2100 | dependencies = [ 2101 | "async_io_stream", 2102 | "futures", 2103 | "js-sys", 2104 | "pharos", 2105 | "send_wrapper", 2106 | "thiserror", 2107 | "wasm-bindgen", 2108 | "wasm-bindgen-futures", 2109 | "web-sys", 2110 | ] 2111 | 2112 | [[package]] 2113 | name = "web-sys" 2114 | version = "0.3.69" 2115 | source = "registry+https://github.com/rust-lang/crates.io-index" 2116 | checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" 2117 | dependencies = [ 2118 | "js-sys", 2119 | "wasm-bindgen", 2120 | ] 2121 | 2122 | [[package]] 2123 | name = "webpki-roots" 2124 | version = "0.26.2" 2125 | source = "registry+https://github.com/rust-lang/crates.io-index" 2126 | checksum = "3c452ad30530b54a4d8e71952716a212b08efd0f3562baa66c29a618b07da7c3" 2127 | dependencies = [ 2128 | "rustls-pki-types", 2129 | ] 2130 | 2131 | [[package]] 2132 | name = "windows-sys" 2133 | version = "0.48.0" 2134 | source = "registry+https://github.com/rust-lang/crates.io-index" 2135 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 2136 | dependencies = [ 2137 | "windows-targets 0.48.5", 2138 | ] 2139 | 2140 | [[package]] 2141 | name = "windows-sys" 2142 | version = "0.52.0" 2143 | source = "registry+https://github.com/rust-lang/crates.io-index" 2144 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 2145 | dependencies = [ 2146 | "windows-targets 0.52.5", 2147 | ] 2148 | 2149 | [[package]] 2150 | name = "windows-targets" 2151 | version = "0.48.5" 2152 | source = "registry+https://github.com/rust-lang/crates.io-index" 2153 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 2154 | dependencies = [ 2155 | "windows_aarch64_gnullvm 0.48.5", 2156 | "windows_aarch64_msvc 0.48.5", 2157 | "windows_i686_gnu 0.48.5", 2158 | "windows_i686_msvc 0.48.5", 2159 | "windows_x86_64_gnu 0.48.5", 2160 | "windows_x86_64_gnullvm 0.48.5", 2161 | "windows_x86_64_msvc 0.48.5", 2162 | ] 2163 | 2164 | [[package]] 2165 | name = "windows-targets" 2166 | version = "0.52.5" 2167 | source = "registry+https://github.com/rust-lang/crates.io-index" 2168 | checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" 2169 | dependencies = [ 2170 | "windows_aarch64_gnullvm 0.52.5", 2171 | "windows_aarch64_msvc 0.52.5", 2172 | "windows_i686_gnu 0.52.5", 2173 | "windows_i686_gnullvm", 2174 | "windows_i686_msvc 0.52.5", 2175 | "windows_x86_64_gnu 0.52.5", 2176 | "windows_x86_64_gnullvm 0.52.5", 2177 | "windows_x86_64_msvc 0.52.5", 2178 | ] 2179 | 2180 | [[package]] 2181 | name = "windows_aarch64_gnullvm" 2182 | version = "0.48.5" 2183 | source = "registry+https://github.com/rust-lang/crates.io-index" 2184 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 2185 | 2186 | [[package]] 2187 | name = "windows_aarch64_gnullvm" 2188 | version = "0.52.5" 2189 | source = "registry+https://github.com/rust-lang/crates.io-index" 2190 | checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" 2191 | 2192 | [[package]] 2193 | name = "windows_aarch64_msvc" 2194 | version = "0.48.5" 2195 | source = "registry+https://github.com/rust-lang/crates.io-index" 2196 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 2197 | 2198 | [[package]] 2199 | name = "windows_aarch64_msvc" 2200 | version = "0.52.5" 2201 | source = "registry+https://github.com/rust-lang/crates.io-index" 2202 | checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" 2203 | 2204 | [[package]] 2205 | name = "windows_i686_gnu" 2206 | version = "0.48.5" 2207 | source = "registry+https://github.com/rust-lang/crates.io-index" 2208 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 2209 | 2210 | [[package]] 2211 | name = "windows_i686_gnu" 2212 | version = "0.52.5" 2213 | source = "registry+https://github.com/rust-lang/crates.io-index" 2214 | checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" 2215 | 2216 | [[package]] 2217 | name = "windows_i686_gnullvm" 2218 | version = "0.52.5" 2219 | source = "registry+https://github.com/rust-lang/crates.io-index" 2220 | checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" 2221 | 2222 | [[package]] 2223 | name = "windows_i686_msvc" 2224 | version = "0.48.5" 2225 | source = "registry+https://github.com/rust-lang/crates.io-index" 2226 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 2227 | 2228 | [[package]] 2229 | name = "windows_i686_msvc" 2230 | version = "0.52.5" 2231 | source = "registry+https://github.com/rust-lang/crates.io-index" 2232 | checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" 2233 | 2234 | [[package]] 2235 | name = "windows_x86_64_gnu" 2236 | version = "0.48.5" 2237 | source = "registry+https://github.com/rust-lang/crates.io-index" 2238 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 2239 | 2240 | [[package]] 2241 | name = "windows_x86_64_gnu" 2242 | version = "0.52.5" 2243 | source = "registry+https://github.com/rust-lang/crates.io-index" 2244 | checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" 2245 | 2246 | [[package]] 2247 | name = "windows_x86_64_gnullvm" 2248 | version = "0.48.5" 2249 | source = "registry+https://github.com/rust-lang/crates.io-index" 2250 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 2251 | 2252 | [[package]] 2253 | name = "windows_x86_64_gnullvm" 2254 | version = "0.52.5" 2255 | source = "registry+https://github.com/rust-lang/crates.io-index" 2256 | checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" 2257 | 2258 | [[package]] 2259 | name = "windows_x86_64_msvc" 2260 | version = "0.48.5" 2261 | source = "registry+https://github.com/rust-lang/crates.io-index" 2262 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 2263 | 2264 | [[package]] 2265 | name = "windows_x86_64_msvc" 2266 | version = "0.52.5" 2267 | source = "registry+https://github.com/rust-lang/crates.io-index" 2268 | checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" 2269 | 2270 | [[package]] 2271 | name = "winreg" 2272 | version = "0.52.0" 2273 | source = "registry+https://github.com/rust-lang/crates.io-index" 2274 | checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" 2275 | dependencies = [ 2276 | "cfg-if", 2277 | "windows-sys 0.48.0", 2278 | ] 2279 | 2280 | [[package]] 2281 | name = "write16" 2282 | version = "1.0.0" 2283 | source = "registry+https://github.com/rust-lang/crates.io-index" 2284 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 2285 | 2286 | [[package]] 2287 | name = "writeable" 2288 | version = "0.5.5" 2289 | source = "registry+https://github.com/rust-lang/crates.io-index" 2290 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 2291 | 2292 | [[package]] 2293 | name = "yoke" 2294 | version = "0.7.4" 2295 | source = "registry+https://github.com/rust-lang/crates.io-index" 2296 | checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" 2297 | dependencies = [ 2298 | "serde", 2299 | "stable_deref_trait", 2300 | "yoke-derive", 2301 | "zerofrom", 2302 | ] 2303 | 2304 | [[package]] 2305 | name = "yoke-derive" 2306 | version = "0.7.4" 2307 | source = "registry+https://github.com/rust-lang/crates.io-index" 2308 | checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" 2309 | dependencies = [ 2310 | "proc-macro2", 2311 | "quote", 2312 | "syn", 2313 | "synstructure", 2314 | ] 2315 | 2316 | [[package]] 2317 | name = "zerocopy" 2318 | version = "0.7.34" 2319 | source = "registry+https://github.com/rust-lang/crates.io-index" 2320 | checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" 2321 | dependencies = [ 2322 | "zerocopy-derive", 2323 | ] 2324 | 2325 | [[package]] 2326 | name = "zerocopy-derive" 2327 | version = "0.7.34" 2328 | source = "registry+https://github.com/rust-lang/crates.io-index" 2329 | checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" 2330 | dependencies = [ 2331 | "proc-macro2", 2332 | "quote", 2333 | "syn", 2334 | ] 2335 | 2336 | [[package]] 2337 | name = "zerofrom" 2338 | version = "0.1.4" 2339 | source = "registry+https://github.com/rust-lang/crates.io-index" 2340 | checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" 2341 | dependencies = [ 2342 | "zerofrom-derive", 2343 | ] 2344 | 2345 | [[package]] 2346 | name = "zerofrom-derive" 2347 | version = "0.1.4" 2348 | source = "registry+https://github.com/rust-lang/crates.io-index" 2349 | checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" 2350 | dependencies = [ 2351 | "proc-macro2", 2352 | "quote", 2353 | "syn", 2354 | "synstructure", 2355 | ] 2356 | 2357 | [[package]] 2358 | name = "zeroize" 2359 | version = "1.8.1" 2360 | source = "registry+https://github.com/rust-lang/crates.io-index" 2361 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2362 | 2363 | [[package]] 2364 | name = "zerovec" 2365 | version = "0.10.2" 2366 | source = "registry+https://github.com/rust-lang/crates.io-index" 2367 | checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" 2368 | dependencies = [ 2369 | "yoke", 2370 | "zerofrom", 2371 | "zerovec-derive", 2372 | ] 2373 | 2374 | [[package]] 2375 | name = "zerovec-derive" 2376 | version = "0.10.2" 2377 | source = "registry+https://github.com/rust-lang/crates.io-index" 2378 | checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" 2379 | dependencies = [ 2380 | "proc-macro2", 2381 | "quote", 2382 | "syn", 2383 | ] 2384 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nostr-tool" 3 | version = "0.5.1" 4 | edition = "2021" 5 | description = "A CLI tool to interact with nostr" 6 | authors = ["0xtr "] 7 | readme = "README.md" 8 | homepage = "https://github.com/0xtrr/nostr-tool" 9 | repository = "https://github.com/0xtrr/nostr-tool" 10 | license = "MIT" 11 | keywords = ["nostr", "tool"] 12 | categories = ["command-line-utilities"] 13 | 14 | [dependencies] 15 | clap = { version = "4.5.6", features = ["derive"] } 16 | csv = "1.3.0" 17 | nostr-sdk = "0.32.0" 18 | num_cpus = "1.16.0" 19 | serde = { version = "1.0.203", features = ["derive"] } 20 | serde_json = "1.0.117" 21 | tokio = { version = "1.38.0", features = ["full"] } 22 | url = "2.5.0" 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.67 as builder 2 | WORKDIR /usr/src/myapp 3 | COPY . . 4 | RUN cargo install --path . 5 | 6 | FROM debian:bullseye-slim 7 | COPY --from=builder /usr/local/cargo/bin/nostr-tool /usr/local/bin/nostr-tool 8 | CMD ["nostr-tool"] 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 0xtr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nostr-tool 2 | 3 |

4 | banner 5 |

6 | 7 | [![crates.io](https://img.shields.io/crates/v/nostr-tool.svg)](https://crates.io/crates/nostr-tool) 8 | [![crates.io - Downloads](https://img.shields.io/crates/d/nostr-tool)](https://crates.io/crates/nostr-tool) 9 | [![Docker Pulls](https://img.shields.io/docker/pulls/0xtr/nostr-tool)](https://hub.docker.com/r/0xtr/nostr-tool) 10 | [![MIT](https://img.shields.io/crates/l/nostr-tool.svg)](LICENSE) 11 | 12 | A CLI tool to make it simple to specific send nostr events. 13 | 14 | ## Install 15 | Installation instructions can be found in [INSTALL.md](docs/INSTALL.md). 16 | 17 | ## Usage 18 | Run `nostr-tool -h` to see available options and subcommands. To get help with the sub commands, run `nostr-tool [subcommand] -h`, e.g. `nostr-tool text-note -h`. 19 | More examples can be found in [EXAMPLES.md](docs/EXAMPLES.md). 20 | 21 | ## Contributions 22 | 23 | Contributions are always welcome! If you find something the CLI is missing, a PR is very appreciated. If you can't 24 | develop it yourself, please make an issue for it so that me or someone else can take a stab at it. 25 | 26 | Thank you for your contributions! Your help is greatly appreciated. 27 | 28 | -------------------------------------------------------------------------------- /docs/EXAMPLES.md: -------------------------------------------------------------------------------- 1 | ## Examples 2 | 3 | ### Update metadata 4 | ```shell 5 | nostr-tool -r wss://nostr.oxtr.dev update-metadata -n "Alice" -a "Who the fuck is Alice?" -p "https://upload.wikimedia.org/wikipedia/en/2/2b/New_world-living_next_door_to_alice.JPG" 6 | ``` 7 | 8 | ### Create a new note with a new identity 9 | 10 | ```shell 11 | nostr-tool -r wss://nostr.oxtr.dev text-note -c "Hello World" 12 | ``` 13 | 14 | ### Create a new note with an existing private key 15 | 16 | ```shell 17 | nostr-tool -r wss://nostr.oxtr.dev -p {PRIVATE_KEY} text-note -c "Hello World" 18 | ``` 19 | 20 | ### Create a new note with an existing private key as a reply to another note 21 | 22 | ```shell 23 | nostr-tool -r wss://nostr.oxtr.dev -p {PRIVATE_KEY} text-note -c "Hello World" --etag {EVENT-ID_TO_REPLY_TO} --ptag {PUBKEY_YOU_ARE_REPLYING_TO} 24 | ``` 25 | 26 | ### Import contacts/followers from a CSV file 27 | 28 | ```shell 29 | nostr-tool -r wss://nostr.oxtr.dev -p {PRIVATE_KEY} publish-contact-list-csv -f {PATH_TO_CSV_FILE} 30 | ``` 31 | 32 | The CSV file should have the following format 33 | ```csv 34 | pubkey,relay,petname 35 | b2d670de53b27691c0c3400225b65c35a26d06093bcc41f48ffc71e0907f9d4a,"wss://nostr.oxtr.dev","" 36 | 32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245,"wss://relay.damus.io","" 37 | ``` 38 | 39 | ### Send a direct message 40 | 41 | ```shell 42 | nostr-tool -r wss://nostr.oxtr.dev -p {PRIVATE_KEY} send-direct-message --receiver {RECIPIENT_PUBKEY} --message "Hello World" 43 | ``` 44 | 45 | ### Delete an event 46 | 47 | ```shell 48 | nostr-tool -r wss://nostr.oxtr.dev -p {PRIVATE_KEY} delete-event -e {EVENT_ID} -r "The reason for deleting the event" 49 | ``` 50 | 51 | ### Delete a profile 52 | 53 | Just events: 54 | ```shell 55 | nostr-tool -r wss://nostr.oxtr.dev -p {PRIVATE_KEY} delete-profile --events-only --kinds 1 56 | ``` 57 | 58 | Delete metadata profile: 59 | ```shell 60 | nostr-tool -r wss://nostr.oxtr.dev -p {PRIVATE_KEY} delete-profile 61 | ``` 62 | 63 | ### React to an event 64 | 65 | ```shell 66 | nostr-tool -r wss://nostr.oxtr.dev -p {PRIVATE_KEY} react -e {EVENT_ID} -a {EVENT_AUTHOR_PUBKEY} -r "👍" 67 | ``` 68 | 69 | ### Run with docker 70 | ```shell 71 | docker run nostr-tool nostr-tool -r wss://nostr.oxtr.dev text-note -c "Hello World" 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/INSTALL.md: -------------------------------------------------------------------------------- 1 | Clone the repo and run the following command in the repo folder. You must have Rust installed to compile this. 2 | 3 | ### Install from crates.io 4 | ```shell 5 | cargo install nostr-tool 6 | ``` 7 | 8 | ### Build from source 9 | ```shell 10 | cargo build --release 11 | ``` 12 | 13 | ### Build with Docker locally 14 | ```shell 15 | docker build -t nostr-tool . 16 | ``` 17 | ### Build with Docker from DockerHub 18 | ```shell 19 | docker pull 0xtr/nostr-tool:0.3.0 20 | ``` 21 | 22 | ### Build with Nix locally 23 | ```shell 24 | nix develop 25 | cargo build --release 26 | ``` -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1710146030, 9 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1719254875, 24 | "narHash": "sha256-ECni+IkwXjusHsm9Sexdtq8weAq/yUyt1TWIemXt3Ko=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "2893f56de08021cffd9b6b6dfc70fd9ccd51eb60", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "id": "nixpkgs", 32 | "ref": "nixos-unstable", 33 | "type": "indirect" 34 | } 35 | }, 36 | "root": { 37 | "inputs": { 38 | "flake-utils": "flake-utils", 39 | "nixpkgs": "nixpkgs", 40 | "rust-overlay": "rust-overlay" 41 | } 42 | }, 43 | "rust-overlay": { 44 | "inputs": { 45 | "nixpkgs": [ 46 | "nixpkgs" 47 | ] 48 | }, 49 | "locked": { 50 | "lastModified": 1719454714, 51 | "narHash": "sha256-MojqG0lyUINkEk0b3kM2drsU5vyaF8DFZe/FAlZVOGs=", 52 | "owner": "oxalica", 53 | "repo": "rust-overlay", 54 | "rev": "d1c527659cf076ecc4b96a91c702d080b213801e", 55 | "type": "github" 56 | }, 57 | "original": { 58 | "owner": "oxalica", 59 | "repo": "rust-overlay", 60 | "type": "github" 61 | } 62 | }, 63 | "systems": { 64 | "locked": { 65 | "lastModified": 1681028828, 66 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 67 | "owner": "nix-systems", 68 | "repo": "default", 69 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 70 | "type": "github" 71 | }, 72 | "original": { 73 | "owner": "nix-systems", 74 | "repo": "default", 75 | "type": "github" 76 | } 77 | } 78 | }, 79 | "root": "root", 80 | "version": 7 81 | } 82 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Nostr-tool flake"; 3 | 4 | inputs = { 5 | flake-utils.url = "github:numtide/flake-utils"; 6 | rust-overlay = { 7 | url = "github:oxalica/rust-overlay"; 8 | inputs.nixpkgs.follows = "nixpkgs"; 9 | }; 10 | nixpkgs.url = "nixpkgs/nixos-unstable"; 11 | }; 12 | 13 | outputs = { self, nixpkgs, flake-utils, rust-overlay }: 14 | flake-utils.lib.eachDefaultSystem (system: 15 | let 16 | overlays = [ rust-overlay.overlays.default ]; 17 | pkgs = import nixpkgs { inherit system overlays; }; 18 | rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml; 19 | inputs = [ 20 | rust 21 | pkgs.rust-analyzer 22 | pkgs.openssl 23 | pkgs.zlib 24 | pkgs.gcc 25 | pkgs.pkg-config 26 | pkgs.clang 27 | ]; 28 | in 29 | { 30 | packages.default = pkgs.rustPlatform.buildRustPackage { 31 | name = "nostr-tool"; 32 | src = ./.; 33 | cargoLock = { 34 | lockFile = ./Cargo.lock; 35 | }; 36 | nativeBuildInputs = inputs; 37 | }; 38 | formatter = pkgs.nixpkgs-fmt; 39 | 40 | devShells.default = pkgs.mkShell { 41 | packages = inputs; 42 | shellHook = '' 43 | export LIBCLANG_PATH=${pkgs.libclang.lib}/lib/ 44 | export LD_LIBRARY_PATH=${pkgs.openssl}/lib:$LD_LIBRARY_PATH 45 | ''; 46 | }; 47 | } 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /resources/contact_list.csv: -------------------------------------------------------------------------------- 1 | pubkey,relay,petname 2 | b2d670de53b27691c0c3400225b65c35a26d06093bcc41f48ffc71e0907f9d4a,"wss://nostr.oxtr.dev","" 3 | 32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245,"wss://relay.damus.io","" -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.78" 3 | components = ["clippy", "rustfmt"] 4 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::{Parser, Subcommand}; 2 | use nostr_sdk::Result; 3 | 4 | mod sub_commands; 5 | mod utils; 6 | 7 | /// Simple CLI application to interact with nostr 8 | #[derive(Parser)] 9 | #[command(name = "nostr-tool")] 10 | #[command(author = "0xtr. , 19 | /// Relay to connect to 20 | #[arg(short, long, action = clap::ArgAction::Append)] 21 | relays: Vec, 22 | /// Proof of work difficulty target 23 | #[arg(short, long, action = clap::ArgAction::Append, default_value_t = 0)] 24 | difficulty_target: u8, 25 | } 26 | 27 | #[derive(Subcommand)] 28 | enum Commands { 29 | /// Set metadata. Be aware that this will simply replace your current kind 0 event. 30 | SetMetadata(sub_commands::set_metadata::SetMetadataSubCommand), 31 | /// Send text note 32 | TextNote(sub_commands::text_note::TextNoteSubCommand), 33 | /// Publish contacts from a CSV file 34 | PublishContactListCsv(sub_commands::publish_contactlist_csv::PublishContactListCsvSubCommand), 35 | /// Delete an event 36 | DeleteEvent(sub_commands::delete_event::DeleteEventSubCommand), 37 | /// Delete a profile 38 | DeleteProfile(sub_commands::delete_profile::DeleteProfileSubCommand), 39 | /// React to an event 40 | React(sub_commands::react::ReactionSubCommand), 41 | /// Get all events 42 | ListEvents(sub_commands::list_events::ListEventsSubCommand), 43 | /// Generate a new keypair 44 | GenerateKeypair(sub_commands::generate_keypair::GenerateKeypairSubCommand), 45 | /// Convert key from bech32 to hex or hex to bech32 46 | ConvertKey(sub_commands::convert_key::ConvertKeySubCommand), 47 | /// Vanity public key mining 48 | Vanity(sub_commands::vanity::VanitySubCommand), 49 | /// Create a new public channel 50 | CreatePublicChannel(sub_commands::create_public_channel::CreatePublicChannelSubCommand), 51 | /// Update channel metadata 52 | SetChannelMetadata(sub_commands::set_channel_metadata::SetChannelMetadataSubCommand), 53 | /// Send a message to a public channel 54 | SendChannelMessage(sub_commands::send_channel_message::SendChannelMessageSubCommand), 55 | /// Hide a message in a public chat room 56 | HidePublicChannelMessage( 57 | sub_commands::hide_public_channel_message::HidePublicChannelMessageSubCommand, 58 | ), 59 | /// Mute a public key 60 | MutePublicKey(sub_commands::mute_publickey::MutePublickeySubCommand), 61 | /// Broadcast events from file 62 | BroadcastEvents(sub_commands::broadcast_events::BroadcastEventsSubCommand), 63 | /// Create a new badge 64 | CreateBadge(sub_commands::create_badge::CreateBadgeSubCommand), 65 | /// Publish award badge event 66 | AwardBadge(sub_commands::award_badge::AwardBadgeSubCommand), 67 | /// Set profile badges 68 | ProfileBadges(sub_commands::profile_badges::ProfileBadgesSubCommand), 69 | /// Create custom event 70 | CustomEvent(sub_commands::custom_event::CustomEventCommand), 71 | /// Create a user status event 72 | SetUserStatus(sub_commands::user_status::UserStatusSubCommand), 73 | } 74 | 75 | #[tokio::main] 76 | async fn main() -> Result<()> { 77 | // Parse input 78 | let args: Cli = Cli::parse(); 79 | 80 | // Post event 81 | match &args.command { 82 | Commands::SetMetadata(sub_command_args) => { 83 | { 84 | sub_commands::set_metadata::set_metadata( 85 | args.private_key, 86 | args.relays, 87 | args.difficulty_target, 88 | sub_command_args, 89 | ) 90 | } 91 | .await 92 | } 93 | Commands::TextNote(sub_command_args) => { 94 | sub_commands::text_note::broadcast_textnote( 95 | args.private_key, 96 | args.relays, 97 | args.difficulty_target, 98 | sub_command_args, 99 | ) 100 | .await 101 | } 102 | Commands::PublishContactListCsv(sub_command_args) => { 103 | sub_commands::publish_contactlist_csv::publish_contact_list_from_csv_file( 104 | args.private_key, 105 | args.relays, 106 | args.difficulty_target, 107 | sub_command_args, 108 | ) 109 | .await 110 | } 111 | Commands::DeleteEvent(sub_command_args) => { 112 | sub_commands::delete_event::delete( 113 | args.private_key, 114 | args.relays, 115 | args.difficulty_target, 116 | sub_command_args, 117 | ) 118 | .await 119 | } 120 | Commands::DeleteProfile(sub_command_args) => { 121 | sub_commands::delete_profile::delete( 122 | args.private_key, 123 | args.relays, 124 | args.difficulty_target, 125 | sub_command_args, 126 | ) 127 | .await 128 | } 129 | Commands::React(sub_command_args) => { 130 | sub_commands::react::react_to_event( 131 | args.private_key, 132 | args.relays, 133 | args.difficulty_target, 134 | sub_command_args, 135 | ) 136 | .await 137 | } 138 | Commands::ListEvents(sub_command_args) => { 139 | sub_commands::list_events::list_events(args.relays, sub_command_args).await 140 | } 141 | Commands::GenerateKeypair(sub_command_args) => { 142 | sub_commands::generate_keypair::get_new_keypair(sub_command_args).await 143 | } 144 | Commands::ConvertKey(sub_command_args) => { 145 | sub_commands::convert_key::convert_key(sub_command_args).await 146 | } 147 | Commands::Vanity(sub_command_args) => sub_commands::vanity::vanity(sub_command_args).await, 148 | Commands::CreatePublicChannel(sub_command_args) => { 149 | sub_commands::create_public_channel::create_public_channel( 150 | args.private_key, 151 | args.relays, 152 | args.difficulty_target, 153 | sub_command_args, 154 | ) 155 | .await 156 | } 157 | Commands::SetChannelMetadata(sub_command_args) => { 158 | sub_commands::set_channel_metadata::set_channel_metadata( 159 | args.private_key, 160 | args.relays, 161 | args.difficulty_target, 162 | sub_command_args, 163 | ) 164 | .await 165 | } 166 | Commands::SendChannelMessage(sub_command_args) => { 167 | sub_commands::send_channel_message::send_channel_message( 168 | args.private_key, 169 | args.relays, 170 | args.difficulty_target, 171 | sub_command_args, 172 | ) 173 | .await 174 | } 175 | Commands::HidePublicChannelMessage(sub_command_args) => { 176 | sub_commands::hide_public_channel_message::hide_public_channel_message( 177 | args.private_key, 178 | args.relays, 179 | args.difficulty_target, 180 | sub_command_args, 181 | ) 182 | .await 183 | } 184 | Commands::MutePublicKey(sub_command_args) => { 185 | sub_commands::mute_publickey::mute_publickey( 186 | args.private_key, 187 | args.relays, 188 | args.difficulty_target, 189 | sub_command_args, 190 | ) 191 | .await 192 | } 193 | Commands::BroadcastEvents(sub_command_args) => { 194 | sub_commands::broadcast_events::broadcast_events(args.relays, sub_command_args).await 195 | } 196 | Commands::CreateBadge(sub_command_args) => { 197 | sub_commands::create_badge::create_badge( 198 | args.private_key, 199 | args.relays, 200 | args.difficulty_target, 201 | sub_command_args, 202 | ) 203 | .await 204 | } 205 | Commands::AwardBadge(sub_command_args) => { 206 | sub_commands::award_badge::award_badge( 207 | args.private_key, 208 | args.relays, 209 | args.difficulty_target, 210 | sub_command_args, 211 | ) 212 | .await 213 | } 214 | Commands::ProfileBadges(sub_command_args) => { 215 | sub_commands::profile_badges::set_profile_badges( 216 | args.private_key, 217 | args.relays, 218 | args.difficulty_target, 219 | sub_command_args, 220 | ) 221 | .await 222 | } 223 | Commands::CustomEvent(sub_command_args) => { 224 | sub_commands::custom_event::create_custom_event( 225 | args.private_key, 226 | args.relays, 227 | args.difficulty_target, 228 | sub_command_args, 229 | ) 230 | .await 231 | } 232 | Commands::SetUserStatus(sub_command_args) => { 233 | sub_commands::user_status::set_user_status( 234 | args.private_key, 235 | args.relays, 236 | args.difficulty_target, 237 | sub_command_args, 238 | ) 239 | .await 240 | } 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/sub_commands/award_badge.rs: -------------------------------------------------------------------------------- 1 | use std::{process::exit, str::FromStr, time::Duration}; 2 | 3 | use clap::Args; 4 | use nostr_sdk::prelude::*; 5 | 6 | use crate::utils::{create_client, parse_private_key}; 7 | 8 | #[derive(Args)] 9 | pub struct AwardBadgeSubCommand { 10 | /// Badge definition event id 11 | #[arg(short, long)] 12 | badge_event_id: String, 13 | /// Awarded pubkeys 14 | #[arg(short, long, action = clap::ArgAction::Append)] 15 | ptag: Vec, 16 | } 17 | 18 | pub async fn award_badge( 19 | private_key: Option, 20 | relays: Vec, 21 | difficulty_target: u8, 22 | sub_command_args: &AwardBadgeSubCommand, 23 | ) -> Result<()> { 24 | if relays.is_empty() { 25 | panic!("No relays specified, at least one relay is required!") 26 | } 27 | 28 | let keys = parse_private_key(private_key, true).await?; 29 | let client: Client = create_client(&keys, relays, difficulty_target).await?; 30 | 31 | let event_id: EventId = EventId::from_str(sub_command_args.badge_event_id.as_str())?; 32 | let badge_definition_query = client 33 | .get_events_of( 34 | vec![Filter::new().id(event_id)], 35 | Some(Duration::from_secs(10)), 36 | ) 37 | .await?; 38 | 39 | if badge_definition_query.len() != 1 { 40 | eprintln!("Expected one event, got {}", badge_definition_query.len()); 41 | exit(1) 42 | }; 43 | 44 | let badge_definition_event = badge_definition_query.first().unwrap(); 45 | // Verify that this event is a badge definition event 46 | if badge_definition_event.kind != Kind::BadgeDefinition { 47 | eprintln!( 48 | "Unexpected badge definition event. Exepected event of kind {} but got {}", 49 | Kind::BadgeDefinition.as_u32(), 50 | badge_definition_event.kind.as_u32() 51 | ); 52 | exit(1) 53 | } 54 | 55 | // Verify that the user trying to award the badge is actually the author of the badge definition 56 | if badge_definition_event.pubkey != keys.public_key() { 57 | eprint!("Incorrect private key. Only the private key used for issuing the badge definition can award it to other public keys"); 58 | exit(1) 59 | } 60 | 61 | let awarded_pubkeys: Vec = sub_command_args 62 | .ptag 63 | .iter() 64 | .map(|pubkey_string| { 65 | Tag::public_key( 66 | public_key::PublicKey::from_str(pubkey_string).expect("Unable to parse public key"), 67 | ) 68 | }) 69 | .collect(); 70 | 71 | let event = EventBuilder::award_badge(badge_definition_event, awarded_pubkeys)? 72 | .to_pow_event(&keys, difficulty_target)?; 73 | 74 | // Publish event 75 | let event_id = client.send_event(event).await?; 76 | 77 | println!("Published badge award event with id:"); 78 | println!("Hex: {}", event_id.to_hex()); 79 | println!("Bech32: {}", event_id.to_bech32()?); 80 | 81 | Ok(()) 82 | } 83 | -------------------------------------------------------------------------------- /src/sub_commands/broadcast_events.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | use nostr_sdk::prelude::*; 3 | 4 | use crate::utils::{create_client, parse_private_key}; 5 | 6 | #[derive(Args)] 7 | pub struct BroadcastEventsSubCommand { 8 | /// Input file path, should contain an array of JSON events 9 | #[arg(short, long)] 10 | file_path: String, 11 | } 12 | 13 | pub async fn broadcast_events( 14 | relays: Vec, 15 | sub_command_args: &BroadcastEventsSubCommand, 16 | ) -> Result<()> { 17 | if relays.is_empty() { 18 | panic!("No relays specified, at least one relay is required!") 19 | } 20 | 21 | let keys = parse_private_key(None, true).await?; 22 | let client = create_client(&keys, relays.clone(), 0).await?; 23 | 24 | let file = std::fs::File::open(&sub_command_args.file_path)?; 25 | 26 | let events: Vec = serde_json::from_reader(file)?; 27 | 28 | for event in events.clone() { 29 | client.send_event(event).await?; 30 | } 31 | 32 | println!("Published {} events to {:?}", events.len(), relays); 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /src/sub_commands/convert_key.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use clap::Args; 4 | use nostr_sdk::prelude::*; 5 | 6 | use crate::utils::{parse_key_or_id_to_hex_string, Prefix}; 7 | 8 | #[derive(Args)] 9 | pub struct ConvertKeySubCommand { 10 | /// Pubkey in bech32 or hex format 11 | #[arg(short, long)] 12 | key: String, 13 | /// Bech32 prefix. Only used if you're converting from hex to bech32 encoded keys. 14 | #[arg(short, long)] 15 | prefix: Option, 16 | /// Set to true if you're converting from bech32 to hex 17 | #[arg(short, long, default_value = "false")] 18 | to_hex: bool, 19 | } 20 | 21 | pub async fn convert_key(sub_command_args: &ConvertKeySubCommand) -> Result<()> { 22 | if sub_command_args.to_hex { 23 | // Input is bech32 encoded so we find the hex value 24 | let hex_key_or_id = parse_key_or_id_to_hex_string(sub_command_args.key.clone()).await?; 25 | println!("{hex_key_or_id}"); 26 | } else { 27 | // Input is hex so we bech32 encode it based on the provided prefix value 28 | let encoded_key: String = match sub_command_args 29 | .prefix 30 | .as_ref() 31 | .expect("Prefix parameter is missing") 32 | { 33 | Prefix::Npub => PublicKey::from_str(sub_command_args.key.as_str())?.to_bech32()?, 34 | Prefix::Nsec => SecretKey::from_str(sub_command_args.key.as_str())?.to_bech32()?, 35 | Prefix::Note => EventId::from_str(sub_command_args.key.as_str())?.to_bech32()?, 36 | }; 37 | println!("{encoded_key}"); 38 | } 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /src/sub_commands/create_badge.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | use nostr_sdk::prelude::*; 3 | 4 | use crate::utils::{create_client, parse_private_key}; 5 | 6 | #[derive(Args)] 7 | pub struct CreateBadgeSubCommand { 8 | /// Unique identifier for the badge 9 | #[arg(short, long)] 10 | id: String, 11 | /// Badge name 12 | #[arg(short, long)] 13 | name: Option, 14 | /// Badge description 15 | #[arg(short, long)] 16 | description: Option, 17 | /// Badge image url 18 | #[arg(long)] 19 | image_url: Option, 20 | /// Badge image width 21 | #[arg(long)] 22 | image_size_width: Option, 23 | /// Badge image height 24 | #[arg(long)] 25 | image_size_height: Option, 26 | /// Badge thumbnail image url 27 | #[arg(short, long)] 28 | thumb_url: Option, 29 | /// Badge thumbnail width 30 | #[arg(long)] 31 | thumb_size_width: Option, 32 | /// Badge thumbnail height 33 | #[arg(long)] 34 | thumb_size_height: Option, 35 | } 36 | 37 | pub async fn create_badge( 38 | private_key: Option, 39 | relays: Vec, 40 | difficulty_target: u8, 41 | sub_command_args: &CreateBadgeSubCommand, 42 | ) -> Result<()> { 43 | if relays.is_empty() { 44 | panic!("No relays specified, at least one relay is required!") 45 | } 46 | 47 | let keys = parse_private_key(private_key, true).await?; 48 | let client = create_client(&keys, relays, difficulty_target).await?; 49 | 50 | let image_size = match ( 51 | sub_command_args.image_size_height, 52 | sub_command_args.image_size_width, 53 | ) { 54 | (Some(height), Some(width)) => Some(ImageDimensions { height, width }), 55 | _ => None, 56 | }; 57 | 58 | let thumbnails = if let Some(thumb_url) = sub_command_args.thumb_url.clone() { 59 | let thumb_size = match ( 60 | sub_command_args.thumb_size_height, 61 | sub_command_args.thumb_size_width, 62 | ) { 63 | (Some(width), Some(height)) => Some((width, height)), 64 | _ => None, 65 | }; 66 | 67 | let url = UncheckedUrl::from(thumb_url); 68 | 69 | if let Some((width, height)) = thumb_size { 70 | vec![(url, Some(ImageDimensions { width, height }))] 71 | } else { 72 | vec![(url, None)] 73 | } 74 | } else { 75 | Vec::new() 76 | }; 77 | 78 | let image_url: Option = 79 | sub_command_args.image_url.clone().map(UncheckedUrl::from); 80 | 81 | let event = EventBuilder::define_badge( 82 | sub_command_args.id.clone(), 83 | sub_command_args.name.clone(), 84 | sub_command_args.description.clone(), 85 | image_url, 86 | image_size, 87 | thumbnails, 88 | ) 89 | .to_pow_event(&keys, difficulty_target)?; 90 | 91 | // Publish event 92 | let event_id = client.send_event(event).await?; 93 | println!("Published badge definition with id:"); 94 | println!("Hex: {}", event_id.to_hex()); 95 | println!("Bech32: {}", event_id.to_bech32()?); 96 | 97 | Ok(()) 98 | } 99 | -------------------------------------------------------------------------------- /src/sub_commands/create_public_channel.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | use nostr_sdk::prelude::*; 3 | 4 | use crate::utils::{create_client, parse_private_key}; 5 | 6 | #[derive(Args)] 7 | pub struct CreatePublicChannelSubCommand { 8 | /// Channel name 9 | #[arg(short, long)] 10 | name: String, 11 | /// Channel about 12 | #[arg(short, long)] 13 | about: Option, 14 | /// Channel picture 15 | #[arg(short, long)] 16 | picture: Option, 17 | } 18 | 19 | pub async fn create_public_channel( 20 | private_key: Option, 21 | relays: Vec, 22 | difficulty_target: u8, 23 | sub_command_args: &CreatePublicChannelSubCommand, 24 | ) -> Result<()> { 25 | if relays.is_empty() { 26 | panic!("No relays specified, at least one relay is required!") 27 | } 28 | 29 | // Process keypair and create a nostr client 30 | let keys = parse_private_key(private_key, true).await?; 31 | let client = create_client(&keys, relays.clone(), difficulty_target).await?; 32 | 33 | // Create metadata 34 | let mut metadata: Metadata = Metadata::new().name(sub_command_args.name.clone()); 35 | 36 | if let Some(about) = sub_command_args.about.clone() { 37 | metadata = metadata.about(about); 38 | } 39 | 40 | if let Some(picture) = sub_command_args.picture.clone() { 41 | metadata = metadata.picture(Url::parse(picture.as_str()).unwrap()); 42 | } 43 | 44 | // Send event 45 | let event: Event = EventBuilder::channel(&metadata).to_event(&keys).unwrap(); 46 | let event_id = client.send_event(event).await?; 47 | 48 | // Print results 49 | println!("\nCreated new public channel!"); 50 | println!("Channel ID:"); 51 | println!("Hex: {}", event_id.to_hex()); 52 | println!("Bech32: {}", event_id.to_bech32()?); 53 | 54 | Ok(()) 55 | } 56 | -------------------------------------------------------------------------------- /src/sub_commands/custom_event.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use clap::Args; 4 | use nostr_sdk::prelude::*; 5 | 6 | use crate::utils::{create_client, parse_private_key}; 7 | 8 | #[derive(Args)] 9 | pub struct CustomEventCommand { 10 | /// Event kind 11 | #[arg(short, long)] 12 | kind: u16, 13 | 14 | /// Note content 15 | #[arg(short, long)] 16 | content: Option, 17 | 18 | /// Arbitrary tags. Specify first the tag key, then separate each string you want in the array with the character '|'. 19 | /// Example for adding an a-tag: "a|30001:b2d670de53b27691c0c3400225b65c35a26d06093bcc41f48ffc71e0907f9d4a:bookmark|wss://nostr.oxtr.dev" 20 | /// 21 | /// This will result in an array that looks like this: ["a", "30001:b2d670de53b27691c0c3400225b65c35a26d06093bcc41f48ffc71e0907f9d4a:bookmark", "wss://nostr.oxtr.dev"] 22 | #[arg(short, long, action = clap::ArgAction::Append)] 23 | tags: Vec, 24 | 25 | // Print keys as hex 26 | #[arg(long, default_value = "false")] 27 | hex: bool, 28 | } 29 | 30 | pub async fn create_custom_event( 31 | private_key: Option, 32 | relays: Vec, 33 | difficulty_target: u8, 34 | sub_command_args: &CustomEventCommand, 35 | ) -> Result<()> { 36 | if relays.is_empty() { 37 | panic!("No relays specified, at least one relay is required!") 38 | } 39 | 40 | let keys = parse_private_key(private_key, true).await?; 41 | let client = create_client(&keys, relays, difficulty_target).await?; 42 | 43 | // Parse kind input 44 | let kind = Kind::Custom(sub_command_args.kind); 45 | 46 | // Set content 47 | let content = sub_command_args 48 | .content 49 | .clone() 50 | .unwrap_or_else(|| String::from("")); 51 | 52 | // Set up tags 53 | let mut tags: Vec = vec![]; 54 | 55 | for tag in sub_command_args.tags.clone().iter() { 56 | let parts: Vec = tag.split('|').map(String::from).collect(); 57 | let tag_kind = parts.first().unwrap().clone(); 58 | tags.push(Tag::custom( 59 | TagKind::Custom(Cow::from(tag_kind)), 60 | parts[1..].to_vec(), 61 | )); 62 | } 63 | 64 | // Initialize event builder 65 | let event = EventBuilder::new(kind, content, tags).to_pow_event(&keys, difficulty_target)?; 66 | 67 | // Publish event 68 | let event_id = client.send_event(event).await?; 69 | 70 | if !sub_command_args.hex { 71 | println!("Published custom event with id: {}", event_id.to_bech32()?); 72 | } else { 73 | println!("Published custom event with id: {}", event_id.to_hex()); 74 | } 75 | 76 | Ok(()) 77 | } 78 | -------------------------------------------------------------------------------- /src/sub_commands/delete_event.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | use nostr_sdk::prelude::*; 3 | 4 | use crate::utils::{create_client, parse_private_key}; 5 | 6 | #[derive(Args)] 7 | pub struct DeleteEventSubCommand { 8 | /// Event id to delete. Must be in hex format. 9 | #[arg(short, long)] 10 | event_id: String, 11 | /// Print keys as hex 12 | #[arg(long, default_value = "false")] 13 | hex: bool, 14 | } 15 | 16 | pub async fn delete( 17 | private_key: Option, 18 | relays: Vec, 19 | difficulty_target: u8, 20 | sub_command_args: &DeleteEventSubCommand, 21 | ) -> Result<()> { 22 | if relays.is_empty() { 23 | panic!("No relays specified, at least one relay is required!") 24 | } 25 | 26 | let keys = parse_private_key(private_key, true).await?; 27 | let client = create_client(&keys, relays, difficulty_target).await?; 28 | 29 | let event_id_to_delete = EventId::from_hex(sub_command_args.event_id.clone())?; 30 | 31 | let event_id = client.delete_event(event_id_to_delete).await?; 32 | if !sub_command_args.hex { 33 | println!("Deleted event with id: {}", event_id.to_bech32()?); 34 | } else { 35 | println!("Deleted event with id: {}", event_id.to_hex()); 36 | } 37 | Ok(()) 38 | } 39 | -------------------------------------------------------------------------------- /src/sub_commands/delete_profile.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use clap::Args; 4 | use nostr_sdk::prelude::*; 5 | 6 | use crate::utils::{create_client, parse_private_key}; 7 | 8 | #[derive(Args)] 9 | pub struct DeleteProfileSubCommand { 10 | /// Delete just the events instead of the profile 11 | #[arg(long, default_value = "false")] 12 | events_only: bool, 13 | /// If events only are selected, allows specifying kinds 14 | #[arg(short, long, action = clap::ArgAction::Append)] 15 | kinds: Option>, 16 | /// Reason for deleting the events 17 | #[arg(short, long)] 18 | reason: Option, 19 | // Print keys as hex 20 | #[arg(long, default_value = "false")] 21 | hex: bool, 22 | /// Timeout in seconds 23 | #[arg(long)] 24 | timeout: Option, 25 | } 26 | 27 | pub async fn delete( 28 | private_key: Option, 29 | relays: Vec, 30 | difficulty_target: u8, 31 | sub_command_args: &DeleteProfileSubCommand, 32 | ) -> Result<()> { 33 | if relays.is_empty() { 34 | panic!("No relays specified, at least one relay is required!") 35 | } 36 | 37 | let keys = parse_private_key(private_key, true).await?; 38 | let client = create_client(&keys, relays, difficulty_target).await?; 39 | 40 | let timeout = sub_command_args.timeout.map(Duration::from_secs); 41 | 42 | if sub_command_args.events_only { 43 | // go through all of the user events 44 | let authors: Vec = vec![keys.public_key()]; 45 | println!("checking author events..."); 46 | 47 | // Convert kind number to Kind struct 48 | let kinds: Vec = sub_command_args 49 | .kinds 50 | .clone() 51 | .unwrap_or_default() 52 | .into_iter() 53 | .map(|x| x as u16) 54 | .map(Kind::from) 55 | .collect(); 56 | 57 | let events: Vec = client 58 | .get_events_of(vec![Filter::new().authors(authors).kinds(kinds)], timeout) 59 | .await?; 60 | 61 | let event_ids: Vec = events 62 | .iter() 63 | .map(|event| EventIdOrCoordinate::from(event.id)) 64 | .collect::>(); 65 | 66 | println!("Retrieved events to delete: {}", events.len()); 67 | 68 | let delete_event: Event = EventBuilder::delete_with_reason( 69 | event_ids, 70 | sub_command_args.reason.clone().unwrap_or_default(), 71 | ) 72 | .to_pow_event(&keys, difficulty_target) 73 | .unwrap(); 74 | 75 | let event_id = client.send_event(delete_event).await?; 76 | 77 | if !sub_command_args.hex { 78 | println!("All event deleted in event {}", event_id.to_bech32()?); 79 | } else { 80 | println!("All event deleted in event {}", event_id.to_hex()); 81 | } 82 | } else { 83 | // Not a perfect delete but multiple clients trigger off of this metadata 84 | let metadata = Metadata::default() 85 | .name("Deleted") 86 | .display_name("Deleted") 87 | .about("Deleted") 88 | .custom_field("deleted", Value::Bool(true)); 89 | 90 | let event_id = client.set_metadata(&metadata).await?; 91 | println!("Metadata updated ({})", event_id.to_bech32()?); 92 | } 93 | Ok(()) 94 | } 95 | -------------------------------------------------------------------------------- /src/sub_commands/generate_keypair.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | use nostr_sdk::prelude::*; 3 | 4 | #[derive(Args)] 5 | pub struct GenerateKeypairSubCommand { 6 | /// Print keys in hex. Defaults to printing bech32 encoded keys. 7 | #[arg(short, long, default_value = "false")] 8 | print_hex: bool, 9 | } 10 | 11 | pub async fn get_new_keypair(sub_command_args: &GenerateKeypairSubCommand) -> Result<()> { 12 | let keys = Keys::generate(); 13 | if sub_command_args.print_hex { 14 | println!("Private key: {}", keys.secret_key()?.display_secret()); 15 | println!("Public key: {}", keys.public_key()) 16 | } else { 17 | println!("Private key: {}", keys.secret_key()?.to_bech32()?); 18 | println!("Public key: {}", keys.public_key().to_bech32()?); 19 | } 20 | Ok(()) 21 | } 22 | -------------------------------------------------------------------------------- /src/sub_commands/hide_public_channel_message.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | use nostr_sdk::prelude::*; 3 | 4 | use crate::utils::{create_client, parse_private_key}; 5 | 6 | #[derive(Args)] 7 | pub struct HidePublicChannelMessageSubCommand { 8 | /// Reason for hiding 9 | #[arg(short, long)] 10 | reason: Option, 11 | /// Event to hide. Must be in hex format. 12 | #[arg(short, long)] 13 | event_id: String, 14 | /// Print keys as hex 15 | #[arg(long, default_value = "false")] 16 | hex: bool, 17 | } 18 | 19 | pub async fn hide_public_channel_message( 20 | private_key: Option, 21 | relays: Vec, 22 | difficulty_target: u8, 23 | sub_command_args: &HidePublicChannelMessageSubCommand, 24 | ) -> Result<()> { 25 | if relays.is_empty() { 26 | panic!("No relays specified, at least one relay is required!") 27 | } 28 | 29 | let keys = parse_private_key(private_key, true).await?; 30 | let client = create_client(&keys, relays, difficulty_target).await?; 31 | 32 | // Set up eventId 33 | let event_id_to_hide = EventId::from_hex(sub_command_args.event_id.clone())?; 34 | 35 | client 36 | .hide_channel_msg(event_id_to_hide, sub_command_args.reason.clone()) 37 | .await?; 38 | println!("Channel message with id {event_id_to_hide} successfully hidden"); 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /src/sub_commands/list_events.rs: -------------------------------------------------------------------------------- 1 | use std::{str::FromStr, time::Duration}; 2 | 3 | use clap::Args; 4 | use nostr_sdk::prelude::*; 5 | 6 | use crate::utils::create_client; 7 | 8 | #[derive(Args)] 9 | pub struct ListEventsSubCommand { 10 | /// Ids 11 | #[arg(short, long, action = clap::ArgAction::Append)] 12 | ids: Option>, 13 | /// Authors 14 | #[arg(short, long, action = clap::ArgAction::Append)] 15 | authors: Option>, 16 | /// Kinds 17 | #[arg(short, long, action = clap::ArgAction::Append)] 18 | kinds: Option>, 19 | /// e tag 20 | #[arg(long, action = clap::ArgAction::Append)] 21 | etag: Option>, 22 | /// p tag 23 | #[arg(long, action = clap::ArgAction::Append)] 24 | ptag: Option>, 25 | /// d tag 26 | #[arg(long, action = clap::ArgAction::Append)] 27 | dtag: Option>, 28 | /// a tag 29 | #[arg(long, action = clap::ArgAction::Append)] 30 | atag: Option>, 31 | /// Since 32 | #[arg(short, long, action = clap::ArgAction::Append)] 33 | since: Option, 34 | /// Until 35 | #[arg(short, long, action = clap::ArgAction::Append)] 36 | until: Option, 37 | /// Limit 38 | #[arg(short, long, action = clap::ArgAction::Append)] 39 | limit: Option, 40 | /// Output 41 | #[arg(short, long)] 42 | output: Option, 43 | /// Timeout in seconds 44 | #[arg(long)] 45 | timeout: Option, 46 | } 47 | 48 | pub async fn list_events( 49 | relays: Vec, 50 | sub_command_args: &ListEventsSubCommand, 51 | ) -> Result<()> { 52 | if relays.is_empty() { 53 | panic!("No relays specified, at least one relay is required!") 54 | } 55 | 56 | let client = create_client(&Keys::generate(), relays, 0).await?; 57 | let mut filter = Filter::new(); 58 | 59 | // Handle event ids 60 | if sub_command_args.ids.is_some() { 61 | let ids: Vec = sub_command_args 62 | .ids 63 | .clone() 64 | .unwrap_or_default() 65 | .iter() 66 | .map(|id| EventId::from_str(id).unwrap()) 67 | .collect(); 68 | filter = filter.ids(ids); 69 | } 70 | 71 | // Handle author public keys 72 | if sub_command_args.authors.is_some() { 73 | let authors: Vec = sub_command_args 74 | .authors 75 | .clone() 76 | .unwrap_or_default() 77 | .iter() 78 | .map(|author_pubkey| PublicKey::from_str(author_pubkey).unwrap()) 79 | .collect(); 80 | filter = filter.authors(authors); 81 | } 82 | 83 | // Handle kind numbers 84 | if sub_command_args.kinds.is_some() { 85 | // Convert kind number to Kind struct 86 | let kinds: Vec = sub_command_args 87 | .kinds 88 | .clone() 89 | .unwrap_or_default() 90 | .into_iter() 91 | .map(|x| x as u16) 92 | .map(Kind::from) 93 | .collect(); 94 | filter = filter.kinds(kinds); 95 | } 96 | 97 | // Handle e-tags 98 | if sub_command_args.etag.is_some() { 99 | // Convert event id string to EventId struct 100 | let events: Vec = sub_command_args 101 | .etag 102 | .clone() 103 | .unwrap_or_default() 104 | .into_iter() 105 | .map(|e| { 106 | if e.starts_with("note1") { 107 | EventId::from_bech32(e.as_str()).expect("Invalid event id") 108 | } else { 109 | EventId::from_str(e.as_str()).expect("Invalid event id") 110 | } 111 | }) 112 | .collect(); 113 | filter = filter.events(events); 114 | } 115 | 116 | // Handle p-tags 117 | if sub_command_args.ptag.is_some() { 118 | // Convert pubkey strings to XOnlyPublicKey struct 119 | let pubkeys: Vec = sub_command_args 120 | .ptag 121 | .clone() 122 | .unwrap_or_default() 123 | .into_iter() 124 | .map(|p| PublicKey::from_str(p.as_str()).expect("Invalid public key")) 125 | .collect(); 126 | filter = filter.pubkeys(pubkeys); 127 | } 128 | 129 | // Handle d-tags 130 | if sub_command_args.dtag.is_some() { 131 | filter = filter.identifiers(sub_command_args.dtag.clone().unwrap_or_default()); 132 | } 133 | 134 | if sub_command_args.since.is_some() { 135 | filter = filter.since(sub_command_args.since.map(Timestamp::from).unwrap()) 136 | } 137 | 138 | if sub_command_args.until.is_some() { 139 | filter = filter.until(sub_command_args.until.map(Timestamp::from).unwrap()) 140 | } 141 | 142 | if sub_command_args.limit.is_some() { 143 | filter = filter.limit(sub_command_args.limit.unwrap()) 144 | } 145 | 146 | let timeout = sub_command_args.timeout.map(Duration::from_secs); 147 | 148 | let events: Vec = client.get_events_of(vec![filter], timeout).await?; 149 | 150 | if let Some(output) = &sub_command_args.output { 151 | let file = std::fs::File::create(output)?; 152 | serde_json::to_writer_pretty(file, &events)?; 153 | println!("Wrote {} event(s) to {}", events.len(), output); 154 | } else { 155 | println!("{}", serde_json::to_string_pretty(&events)?) 156 | } 157 | 158 | Ok(()) 159 | } 160 | -------------------------------------------------------------------------------- /src/sub_commands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod award_badge; 2 | pub mod broadcast_events; 3 | pub mod convert_key; 4 | pub mod create_badge; 5 | pub mod create_public_channel; 6 | pub mod custom_event; 7 | pub mod delete_event; 8 | pub mod delete_profile; 9 | pub mod generate_keypair; 10 | pub mod hide_public_channel_message; 11 | pub mod list_events; 12 | pub mod mute_publickey; 13 | pub mod profile_badges; 14 | pub mod publish_contactlist_csv; 15 | pub mod react; 16 | pub mod send_channel_message; 17 | pub mod set_channel_metadata; 18 | pub mod set_metadata; 19 | pub mod text_note; 20 | pub mod user_status; 21 | pub mod vanity; 22 | -------------------------------------------------------------------------------- /src/sub_commands/mute_publickey.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use clap::Args; 4 | use nostr_sdk::prelude::*; 5 | 6 | use crate::utils::{create_client, parse_private_key}; 7 | 8 | #[derive(Args)] 9 | pub struct MutePublickeySubCommand { 10 | /// Reason for muting 11 | #[arg(short, long)] 12 | reason: Option, 13 | /// Public key to mute. Must be in hex format. 14 | #[arg(short, long)] 15 | public_key: String, 16 | } 17 | 18 | pub async fn mute_publickey( 19 | private_key: Option, 20 | relays: Vec, 21 | difficulty_target: u8, 22 | sub_command_args: &MutePublickeySubCommand, 23 | ) -> Result<()> { 24 | if relays.is_empty() { 25 | panic!("No relays specified, at least one relay is required!") 26 | } 27 | 28 | let keys = parse_private_key(private_key, true).await?; 29 | let client = create_client(&keys, relays, difficulty_target).await?; 30 | 31 | // Set up pubkey to mute 32 | let pubkey_to_mute = key::PublicKey::from_str(sub_command_args.public_key.as_str())?; 33 | 34 | let event_id = client 35 | .mute_channel_user(pubkey_to_mute, sub_command_args.reason.clone()) 36 | .await?; 37 | 38 | println!("Public key {} muted in event {}", pubkey_to_mute, event_id); 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /src/sub_commands/profile_badges.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | use std::time::Duration; 3 | 4 | use clap::Args; 5 | use nostr_sdk::prelude::*; 6 | 7 | use crate::utils::{create_client, parse_private_key}; 8 | 9 | #[derive(Args)] 10 | pub struct ProfileBadgesSubCommand { 11 | /// Badge definition event id 12 | #[arg(short, long, action = clap::ArgAction::Append)] 13 | badge_id: Vec, 14 | /// Badge award event id 15 | #[arg(short, long, action = clap::ArgAction::Append)] 16 | award_id: Vec, 17 | } 18 | 19 | pub async fn set_profile_badges( 20 | private_key: Option, 21 | relays: Vec, 22 | difficulty_target: u8, 23 | sub_command_args: &ProfileBadgesSubCommand, 24 | ) -> Result<()> { 25 | if relays.is_empty() { 26 | panic!("No relays specified, at least one relay is required!") 27 | } 28 | 29 | let keys = parse_private_key(private_key, true).await?; 30 | let client: Client = create_client(&keys, relays, difficulty_target).await?; 31 | 32 | let badge_definition_event_ids: Vec = sub_command_args 33 | .badge_id 34 | .iter() 35 | .map(|badge_id| EventId::from_str(badge_id).unwrap()) 36 | .collect(); 37 | let badge_definition_filter = Filter::new() 38 | .ids(badge_definition_event_ids) 39 | .kind(Kind::BadgeDefinition); 40 | let badge_defintion_events = client 41 | .get_events_of(vec![badge_definition_filter], Some(Duration::from_secs(10))) 42 | .await 43 | .unwrap(); 44 | 45 | let award_event_ids: Vec = sub_command_args 46 | .award_id 47 | .iter() 48 | .map(|award_event_id| EventId::from_str(award_event_id).unwrap()) 49 | .collect(); 50 | let badge_award_filter = Filter::new().ids(award_event_ids).kind(Kind::BadgeAward); 51 | let badge_award_events = client 52 | .get_events_of(vec![badge_award_filter], Some(Duration::from_secs(10))) 53 | .await 54 | .unwrap(); 55 | 56 | let event = EventBuilder::profile_badges( 57 | badge_defintion_events, 58 | badge_award_events, 59 | &keys.public_key(), 60 | )? 61 | .to_pow_event(&keys, difficulty_target)?; 62 | 63 | // Publish event 64 | let event_id = client.send_event(event).await?; 65 | println!("Published profile badges event with id:"); 66 | println!("Hex: {}", event_id.to_hex()); 67 | println!("Bech32: {}", event_id.to_bech32()?); 68 | 69 | Ok(()) 70 | } 71 | -------------------------------------------------------------------------------- /src/sub_commands/publish_contactlist_csv.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use clap::Args; 4 | use nostr_sdk::prelude::*; 5 | use serde::Deserialize; 6 | 7 | use crate::utils::{create_client, parse_private_key}; 8 | 9 | #[derive(Args)] 10 | pub struct PublishContactListCsvSubCommand { 11 | /// Path to CSV file. CSV file should be have the following format: 12 | /// pubkey,relay_url,petname. See example in resources/contact_list.csv 13 | #[arg(short, long)] 14 | filepath: String, 15 | // Print keys as hex 16 | #[arg(long, default_value = "false")] 17 | hex: bool, 18 | } 19 | 20 | // nostr_rust ContactListTag struct does not derive "Deserialize", therefore we need this custom implementation 21 | #[derive(Debug, Clone, Deserialize)] 22 | pub struct ContactListTag { 23 | /// 32-bytes hex key - the public key of the contact 24 | pub pubkey: String, 25 | /// main relay URL 26 | pub relay: Option, 27 | /// Petname 28 | pub petname: Option, 29 | } 30 | 31 | pub async fn publish_contact_list_from_csv_file( 32 | private_key: Option, 33 | relays: Vec, 34 | difficulty_target: u8, 35 | sub_command_args: &PublishContactListCsvSubCommand, 36 | ) -> Result<()> { 37 | if relays.is_empty() { 38 | panic!("No relays specified, at least one relay is required!") 39 | } 40 | 41 | let keys = parse_private_key(private_key, true).await?; 42 | let client = create_client(&keys, relays, difficulty_target).await?; 43 | 44 | let mut rdr = csv::Reader::from_path(&sub_command_args.filepath)?; 45 | let mut contacts: Vec = vec![]; 46 | for result in rdr.deserialize() { 47 | let tag: ContactListTag = result?; 48 | let relay_url = match tag.relay { 49 | Some(relay) => Some(UncheckedUrl::from_str(&relay)?), 50 | None => None, 51 | }; 52 | let clt = Contact { 53 | public_key: PublicKey::from_str(&tag.pubkey)?, 54 | relay_url, 55 | alias: tag.petname, 56 | }; 57 | contacts.push(clt); 58 | } 59 | 60 | client.set_contact_list(contacts).await?; 61 | println!("Contact list imported!"); 62 | Ok(()) 63 | } 64 | -------------------------------------------------------------------------------- /src/sub_commands/react.rs: -------------------------------------------------------------------------------- 1 | use std::process::exit; 2 | use std::time::Duration; 3 | 4 | use clap::Args; 5 | use nostr_sdk::prelude::*; 6 | 7 | use crate::utils::{create_client, parse_private_key}; 8 | 9 | #[derive(Args)] 10 | pub struct ReactionSubCommand { 11 | /// Event id to react to 12 | #[arg(short, long)] 13 | event_id: String, 14 | /// Author pubkey of the event you are reacting to. Must be hex format. 15 | #[arg(short, long)] 16 | author_pubkey: String, 17 | /// Reaction content. Set to '+' for like or '-' for dislike. Single emojis are also often used for reactions, such as in Damus Web. 18 | #[arg(short, long)] 19 | reaction: String, 20 | // Print keys as hex 21 | #[arg(long, default_value = "false")] 22 | hex: bool, 23 | } 24 | 25 | pub async fn react_to_event( 26 | private_key: Option, 27 | relays: Vec, 28 | difficulty_target: u8, 29 | sub_command_args: &ReactionSubCommand, 30 | ) -> Result<()> { 31 | if relays.is_empty() { 32 | panic!("No relays specified, at least one relay is required!") 33 | } 34 | 35 | let keys = parse_private_key(private_key, true).await?; 36 | let client = create_client(&keys, relays, difficulty_target).await?; 37 | 38 | if sub_command_args.reaction.trim().is_empty() { 39 | eprintln!("Reaction does not contain any content"); 40 | exit(0) 41 | } 42 | 43 | let event_id = EventId::from_hex(&sub_command_args.event_id)?; 44 | let author_pubkey = PublicKey::from_hex(sub_command_args.author_pubkey.clone())?; 45 | 46 | let subscription = Filter::new().event(event_id).author(author_pubkey); 47 | 48 | let events = client 49 | .get_events_of_with_opts( 50 | vec![subscription], 51 | Some(Duration::from_secs(30)), 52 | FilterOptions::ExitOnEOSE, 53 | ) 54 | .await?; 55 | 56 | if events.is_empty() { 57 | eprintln!("Unable to find note with the provided event id"); 58 | exit(0); 59 | } 60 | 61 | let event_to_react_to = events.first().unwrap(); 62 | 63 | let id = client 64 | .reaction(event_to_react_to, sub_command_args.reaction.clone()) 65 | .await?; 66 | println!( 67 | "Reacted to {} with {} in event {}", 68 | event_id.to_bech32()?, 69 | sub_command_args.reaction, 70 | id.to_bech32()? 71 | ); 72 | Ok(()) 73 | } 74 | -------------------------------------------------------------------------------- /src/sub_commands/send_channel_message.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::{create_client, parse_private_key}; 2 | use clap::Args; 3 | use nostr_sdk::prelude::*; 4 | 5 | #[derive(Args)] 6 | pub struct SendChannelMessageSubCommand { 7 | /// Channel id to send message to (must be hex) 8 | #[arg(short, long)] 9 | channel_id: String, 10 | /// Message content 11 | #[arg(short, long)] 12 | message: String, 13 | // Print keys as hex 14 | #[arg(long, default_value = "false")] 15 | hex: bool, 16 | } 17 | 18 | pub async fn send_channel_message( 19 | private_key: Option, 20 | relays: Vec, 21 | difficulty_target: u8, 22 | sub_command_args: &SendChannelMessageSubCommand, 23 | ) -> Result<()> { 24 | if relays.is_empty() { 25 | panic!("No relays specified, at least one relay is required!") 26 | } 27 | 28 | // Process keypair and create a nostr client 29 | let keys = parse_private_key(private_key, true).await?; 30 | let client = create_client(&keys, relays.clone(), difficulty_target).await?; 31 | 32 | let ch_id: EventId = EventId::from_hex(sub_command_args.channel_id.clone()).unwrap(); 33 | 34 | let event_id = client 35 | .send_channel_msg( 36 | ch_id, 37 | Url::parse(relays[0].as_str())?, 38 | sub_command_args.message.clone(), 39 | ) 40 | .await?; 41 | println!( 42 | "Public channel message sent with id: {}", 43 | event_id.to_bech32()? 44 | ); 45 | 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /src/sub_commands/set_channel_metadata.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | use nostr_sdk::prelude::*; 3 | 4 | use crate::utils::{create_client, parse_private_key}; 5 | 6 | #[derive(Args)] 7 | pub struct SetChannelMetadataSubCommand { 8 | /// Channel ID 9 | #[arg(short, long)] 10 | channel_id: String, 11 | /// Channel name 12 | #[arg(short, long)] 13 | name: Option, 14 | /// Channel about 15 | #[arg(short, long)] 16 | about: Option, 17 | /// Channel picture 18 | #[arg(short, long)] 19 | picture: Option, 20 | #[arg(short, long)] 21 | recommended_relay: Option, 22 | } 23 | 24 | pub async fn set_channel_metadata( 25 | private_key: Option, 26 | relays: Vec, 27 | difficulty_target: u8, 28 | sub_command_args: &SetChannelMetadataSubCommand, 29 | ) -> Result<()> { 30 | if relays.is_empty() { 31 | panic!("No relays specified, at least one relay is required!") 32 | } 33 | 34 | // Process keypair and create a nostr client 35 | let keys = parse_private_key(private_key, true).await?; 36 | let client = create_client(&keys, relays.clone(), difficulty_target).await?; 37 | 38 | let channel_id: EventId = EventId::from_hex(sub_command_args.channel_id.clone())?; 39 | 40 | // Build metadata 41 | let mut metadata: Metadata = Metadata::new(); 42 | 43 | if let Some(name) = sub_command_args.name.clone() { 44 | metadata = metadata.name(name); 45 | } 46 | 47 | if let Some(about) = sub_command_args.about.clone() { 48 | metadata = metadata.about(about); 49 | } 50 | 51 | if let Some(picture) = sub_command_args.picture.clone() { 52 | metadata = metadata.picture(Url::parse(picture.as_str()).unwrap()); 53 | } 54 | 55 | let relay_url = sub_command_args 56 | .recommended_relay 57 | .clone() 58 | .map(|relay_string| Url::parse(relay_string.as_str()).unwrap()); 59 | 60 | // Build and send event 61 | let event = EventBuilder::channel_metadata(channel_id, relay_url, &metadata).to_event(&keys)?; 62 | let event_id = client.send_event(event.clone()).await?; 63 | 64 | // Print results 65 | println!( 66 | "\nSet new metadata for channel {}!", 67 | sub_command_args.channel_id.as_str() 68 | ); 69 | println!("\nEvent ID:"); 70 | println!("Hex: {}", event_id.to_hex()); 71 | println!("Bech32: {}", event_id.to_bech32()?); 72 | 73 | Ok(()) 74 | } 75 | -------------------------------------------------------------------------------- /src/sub_commands/set_metadata.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | use nostr_sdk::nostr::nips::nip05; 3 | use nostr_sdk::prelude::*; 4 | 5 | use crate::utils::{create_client, parse_private_key}; 6 | 7 | #[derive(Args)] 8 | pub struct SetMetadataSubCommand { 9 | /// Set profile name 10 | #[arg(short, long)] 11 | name: Option, 12 | /// Set your bio 13 | #[arg(short, long)] 14 | about: Option, 15 | /// Set your profile image URL 16 | #[arg(short, long)] 17 | picture: Option, 18 | /// Set your NIP-05 19 | #[arg(long)] 20 | nip05: Option, 21 | /// Set your LUD-06 LNURL 22 | #[arg(long)] 23 | lud06: Option, 24 | /// Set your LUD-16 LN address 25 | #[arg(long)] 26 | lud16: Option, 27 | /// External identities. Use this syntax: "platform:identity:proof" 28 | #[arg(short, long)] 29 | identities: Vec, 30 | /// Arbitrary fields not in the protocol. Use this syntax: "key:value" 31 | #[arg(short, long)] 32 | extra_field: Vec, 33 | /// Print keys as hex 34 | #[arg(long, default_value = "false")] 35 | hex: bool, 36 | } 37 | 38 | pub async fn set_metadata( 39 | private_key: Option, 40 | relays: Vec, 41 | difficulty_target: u8, 42 | sub_command_args: &SetMetadataSubCommand, 43 | ) -> Result<()> { 44 | if relays.is_empty() { 45 | panic!("No relays specified, at least one relay is required!") 46 | } 47 | 48 | let keys = parse_private_key(private_key, true).await?; 49 | let client = create_client(&keys, relays, difficulty_target).await?; 50 | 51 | let mut metadata = Metadata::new(); 52 | 53 | // Name 54 | if let Some(name) = &sub_command_args.name { 55 | metadata = metadata.name(name); 56 | } 57 | 58 | // About 59 | if let Some(about) = &sub_command_args.about { 60 | metadata = metadata.about(about); 61 | } 62 | 63 | // Picture URL 64 | if let Some(picture_url) = &sub_command_args.picture { 65 | let url = Url::parse(picture_url)?; 66 | metadata = metadata.picture(url); 67 | }; 68 | 69 | // NIP-05 identifier 70 | if let Some(nip05_identifier) = &sub_command_args.nip05 { 71 | // Check if the nip05 is valid 72 | nip05::verify(&keys.public_key(), nip05_identifier.as_str(), None).await?; 73 | metadata = metadata.nip05(nip05_identifier); 74 | } 75 | 76 | // LUD-06 string 77 | if let Some(lud06) = &sub_command_args.lud06 { 78 | metadata = metadata.lud06(lud06); 79 | } 80 | 81 | // LUD-16 string 82 | if let Some(lud16) = &sub_command_args.lud16 { 83 | metadata = metadata.lud16(lud16); 84 | } 85 | 86 | // Set custom fields 87 | for ef in sub_command_args.extra_field.iter() { 88 | let sef: Vec<&str> = ef.split(':').collect(); 89 | if sef.len() == 2 { 90 | metadata = metadata.custom_field(sef[0], sef[1]) 91 | } 92 | } 93 | 94 | // External identity tags (NIP-39) 95 | let mut identity_tags: Vec = Vec::new(); 96 | for identity in &sub_command_args.identities { 97 | let parts: Vec<&str> = identity.split(':').collect(); 98 | if parts.len() == 3 { 99 | let platform_identity = format!("{}:{}", parts[0], parts[1]); 100 | let proof = parts[2].to_string(); 101 | let tag = Tag::custom(TagKind::Custom("i".into()), [platform_identity, proof]); 102 | identity_tags.push(tag); 103 | } else { 104 | eprintln!("Invalid identity format: {}", identity); 105 | } 106 | } 107 | 108 | let event = EventBuilder::metadata(&metadata) 109 | .add_tags(identity_tags) 110 | .to_pow_event(&keys, difficulty_target) 111 | .unwrap(); 112 | let event_id = client.send_event(event).await?; 113 | println!("New metadata event: {}", event_id.to_bech32()?); 114 | 115 | Ok(()) 116 | } 117 | -------------------------------------------------------------------------------- /src/sub_commands/text_note.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Add; 2 | use std::str::FromStr; 3 | use std::time::Duration; 4 | 5 | use clap::Args; 6 | use nostr_sdk::prelude::*; 7 | 8 | use crate::utils::{create_client, parse_private_key}; 9 | 10 | #[derive(Args)] 11 | pub struct TextNoteSubCommand { 12 | /// Text note content 13 | #[arg(short, long)] 14 | content: String, 15 | /// Subject tag (NIP-14) 16 | #[arg(short, long)] 17 | subject: Option, 18 | /// Pubkey references. Both hex and bech32 encoded keys are supported. 19 | #[arg(long, action = clap::ArgAction::Append)] 20 | ptag: Vec, 21 | /// Event references 22 | #[arg(long, action = clap::ArgAction::Append)] 23 | etag: Vec, 24 | /// Seconds till expiration (NIP-40) 25 | #[arg(long)] 26 | expiration: Option, 27 | } 28 | 29 | pub async fn broadcast_textnote( 30 | private_key: Option, 31 | relays: Vec, 32 | difficulty_target: u8, 33 | sub_command_args: &TextNoteSubCommand, 34 | ) -> Result<()> { 35 | if relays.is_empty() { 36 | panic!("No relays specified, at least one relay is required!") 37 | } 38 | 39 | let keys = parse_private_key(private_key, true).await?; 40 | let client = create_client(&keys, relays, difficulty_target).await?; 41 | 42 | // Set up tags 43 | let mut tags: Vec = vec![]; 44 | 45 | // Subject tag (NIP-14) 46 | if let Some(subject) = &sub_command_args.subject { 47 | let subject_tag = Tag::custom(TagKind::Subject, vec![subject]); 48 | tags.push(subject_tag); 49 | } 50 | 51 | // Add p-tags 52 | for ptag in sub_command_args.ptag.iter() { 53 | // Parse pubkey to ensure we're sending hex keys 54 | let public_key = PublicKey::from_str(ptag.as_str())?; 55 | tags.push(Tag::public_key(public_key)); 56 | } 57 | // Add e-tags 58 | for etag in sub_command_args.etag.iter() { 59 | let event_id = EventId::from_hex(etag)?; 60 | tags.push(Tag::event(event_id)); 61 | } 62 | // Set expiration tag 63 | if let Some(expiration) = sub_command_args.expiration { 64 | let timestamp = Timestamp::now().add(Duration::from_secs(expiration)); 65 | tags.push(Tag::expiration(timestamp)); 66 | } 67 | 68 | // Publish event 69 | let event_id = client 70 | .publish_text_note(sub_command_args.content.clone(), tags) 71 | .await?; 72 | println!("Published text note with id:"); 73 | println!("Hex: {}", event_id.to_hex()); 74 | println!("Bech32: {}", event_id.to_bech32()?); 75 | 76 | Ok(()) 77 | } 78 | -------------------------------------------------------------------------------- /src/sub_commands/user_status.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Add; 2 | use std::str::FromStr; 3 | use std::time::Duration; 4 | 5 | use clap::Args; 6 | use nostr_sdk::prelude::*; 7 | use nostr_sdk::TagKind::SingleLetter; 8 | 9 | use crate::utils::{create_client, parse_key_or_id_to_hex_string, parse_private_key}; 10 | 11 | #[derive(Args)] 12 | pub struct UserStatusSubCommand { 13 | /// Text note content 14 | #[arg(short, long)] 15 | content: String, 16 | /// Status type (identifier tag) 17 | #[arg(short, long)] 18 | status_type: Option, 19 | /// Pubkey references. Both hex and bech32 encoded keys are supported. 20 | #[arg(short, long)] 21 | ptag: Option, 22 | /// Event references. Both hex and bech32 encoded keys are supported. 23 | #[arg(short, long)] 24 | etag: Option, 25 | /// Reference tag 26 | #[arg(short, long)] 27 | rtag: Option, 28 | /// Seconds till expiration (NIP-40) 29 | #[arg(long)] 30 | expiration: Option, 31 | // Print keys as hex 32 | #[arg(long, default_value = "false")] 33 | hex: bool, 34 | } 35 | 36 | pub async fn set_user_status( 37 | private_key: Option, 38 | relays: Vec, 39 | difficulty_target: u8, 40 | sub_command_args: &UserStatusSubCommand, 41 | ) -> Result<()> { 42 | if relays.is_empty() { 43 | panic!("No relays specified, at least one relay is required!") 44 | } 45 | 46 | let keys = parse_private_key(private_key, true).await?; 47 | let client = create_client(&keys, relays, difficulty_target).await?; 48 | 49 | // Set up tags 50 | let mut tags: Vec = vec![]; 51 | 52 | // Add identifier tag 53 | if let Some(status) = &sub_command_args.status_type { 54 | let status = Tag::identifier(status.to_string()); 55 | tags.push(status); 56 | } 57 | 58 | // Add expiration tag 59 | if let Some(expiration) = sub_command_args.expiration { 60 | let timestamp = Timestamp::now().add(Duration::from_secs(expiration)); 61 | tags.push(Tag::expiration(timestamp)); 62 | } 63 | 64 | // Add p-tag 65 | if let Some(p) = sub_command_args.ptag.clone() { 66 | let pubkey_hex = parse_key_or_id_to_hex_string(p).await?; 67 | let pubkey: PublicKey = PublicKey::from_str(&pubkey_hex)?; 68 | tags.push(Tag::public_key(pubkey)) 69 | } 70 | 71 | // Add e-tag 72 | if let Some(e) = sub_command_args.etag.clone() { 73 | let event_id_hex = parse_key_or_id_to_hex_string(e).await?; 74 | let event_id: EventId = EventId::from_hex(event_id_hex)?; 75 | tags.push(Tag::event(event_id)); 76 | } 77 | 78 | // Add r-tag 79 | if let Some(r) = sub_command_args.rtag.clone() { 80 | tags.push(Tag::custom( 81 | SingleLetter(SingleLetterTag::from_char('r').unwrap()), 82 | vec![r], 83 | )); 84 | } 85 | 86 | // Publish event 87 | let event = EventBuilder::new(Kind::Custom(30315), sub_command_args.content.clone(), tags) 88 | .to_pow_event(&keys, difficulty_target)?; 89 | 90 | let event_id = client.send_event(event).await?; 91 | if !sub_command_args.hex { 92 | println!("Published user status with id: {}", event_id.to_bech32()?); 93 | } else { 94 | println!("Published user status with id: {}", event_id.to_hex()); 95 | } 96 | 97 | Ok(()) 98 | } 99 | -------------------------------------------------------------------------------- /src/sub_commands/vanity.rs: -------------------------------------------------------------------------------- 1 | use clap::Args; 2 | use nostr_sdk::prelude::*; 3 | 4 | #[derive(Args)] 5 | pub struct VanitySubCommand { 6 | /// Prefixes 7 | #[arg(short, long, required = true, action = clap::ArgAction::Append)] 8 | prefixes: Vec, 9 | /// Vanity pubkey in hex format 10 | #[arg(long, default_value_t = false)] 11 | hex: bool, 12 | } 13 | 14 | pub async fn vanity(sub_command_args: &VanitySubCommand) -> Result<()> { 15 | let num_cores = num_cpus::get(); 16 | let keys = Keys::vanity( 17 | sub_command_args.prefixes.clone(), 18 | !sub_command_args.hex, 19 | num_cores, 20 | )?; 21 | 22 | if sub_command_args.hex { 23 | println!("Public key (hex): {}", keys.public_key()); 24 | } else { 25 | println!("Public key: {}", keys.public_key().to_bech32()?); 26 | } 27 | 28 | println!("Private key: {}", keys.secret_key()?.to_bech32()?); 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use nostr_sdk::prelude::*; 4 | 5 | pub async fn parse_private_key(private_key: Option, print_keys: bool) -> Result { 6 | // Parse and validate private key 7 | let keys = match private_key { 8 | Some(pk) => { 9 | if pk.starts_with("nsec") { 10 | Keys::new(SecretKey::from_bech32(pk)?) 11 | } else { 12 | // We assume it's a hex formatted private key 13 | Keys::new(SecretKey::from_hex(pk)?) 14 | } 15 | } 16 | None => { 17 | // create a new identity with a new keypair 18 | println!("No private key provided, generating new identity"); 19 | Keys::generate() 20 | } 21 | }; 22 | 23 | if print_keys { 24 | println!("Private key:"); 25 | println!("{}", keys.secret_key()?.to_bech32()?); 26 | println!("{}", keys.secret_key()?.display_secret()); 27 | 28 | println!("Public key:"); 29 | println!("{}", keys.public_key().to_bech32()?); 30 | println!("{}", keys.public_key()); 31 | } 32 | 33 | Ok(keys) 34 | } 35 | 36 | // Creates the websocket client that is used for communicating with relays 37 | pub async fn create_client(keys: &Keys, relays: Vec, difficulty: u8) -> Result { 38 | let opts = Options::new() 39 | .send_timeout(Some(Duration::from_secs(15))) 40 | .wait_for_send(true) 41 | .difficulty(difficulty); 42 | let client = Client::with_opts(keys, opts); 43 | client.add_relays(relays).await?; 44 | client.connect().await; 45 | Ok(client) 46 | } 47 | 48 | pub async fn parse_key_or_id_to_hex_string( 49 | input: String, 50 | ) -> Result> { 51 | let hex_key_or_id = if input.starts_with("npub") { 52 | PublicKey::from_bech32(input.clone()).unwrap().to_hex() 53 | } else if input.starts_with("nsec") { 54 | SecretKey::from_bech32(input)?.display_secret().to_string() 55 | } else if input.starts_with("note") { 56 | EventId::from_bech32(input)?.to_hex() 57 | } else if input.starts_with("nprofile") { 58 | Nip19Profile::from_bech32(input) 59 | .unwrap() 60 | .public_key 61 | .to_hex() 62 | } else { 63 | // If the key is not bech32 encoded, return it as is 64 | input.clone() 65 | }; 66 | 67 | Ok(hex_key_or_id) 68 | } 69 | 70 | #[derive(clap::ValueEnum, Clone, Debug)] 71 | pub enum Prefix { 72 | Npub, 73 | Nsec, 74 | Note, 75 | } 76 | 77 | #[derive(clap::ValueEnum, Clone, Debug)] 78 | pub enum Keyformat { 79 | Hex, 80 | Bech32, 81 | } 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use super::*; 86 | 87 | #[tokio::test] 88 | async fn test_parse_key_hex_input() { 89 | let hex_key = 90 | String::from("f4deaad98b61fa24d86ef315f1d5d57c1a6a533e1e87e777e5d0b48dcd332cdb"); 91 | let result = parse_key_or_id_to_hex_string(hex_key.clone()).await; 92 | 93 | assert!(result.is_ok()); 94 | assert_eq!(result.unwrap(), hex_key); 95 | } 96 | 97 | #[tokio::test] 98 | async fn test_parse_key_bech32_note_input() { 99 | let bech32_note_id = 100 | String::from("note1h445ule4je70k7kvddate8kpsh2fd6n77esevww5hmgda2qwssjsw957wk"); 101 | 102 | let result = parse_key_or_id_to_hex_string(bech32_note_id).await; 103 | 104 | assert!(result.is_ok()); 105 | assert_eq!( 106 | result.unwrap(), 107 | String::from("bd6b4e7f35967cfb7acc6b7abc9ec185d496ea7ef6619639d4bed0dea80e8425") 108 | ); 109 | } 110 | 111 | #[tokio::test] 112 | async fn test_parse_bech32_public_key_input() { 113 | let bech32_encoded_key = 114 | String::from("npub1ktt8phjnkfmfrsxrgqpztdjuxk3x6psf80xyray0l3c7pyrln49qhkyhz0"); 115 | let result = parse_key_or_id_to_hex_string(bech32_encoded_key).await; 116 | 117 | assert!(result.is_ok()); 118 | assert_eq!( 119 | result.unwrap(), 120 | String::from("b2d670de53b27691c0c3400225b65c35a26d06093bcc41f48ffc71e0907f9d4a") 121 | ); 122 | } 123 | 124 | #[tokio::test] 125 | async fn test_parse_bech32_private_key() { 126 | let bech32_encoded_key = 127 | String::from("nsec1hdeqm0y8vgzuucqv4840h7rlpy4qfu928ulxh3dzj6s2nqupdtzqagtew3"); 128 | let result = parse_key_or_id_to_hex_string(bech32_encoded_key).await; 129 | 130 | assert!(result.is_ok()); 131 | assert_eq!( 132 | result.unwrap(), 133 | String::from("bb720dbc876205ce600ca9eafbf87f092a04f0aa3f3e6bc5a296a0a983816ac4") 134 | ); 135 | } 136 | } 137 | --------------------------------------------------------------------------------