diff --git a/Cargo.lock b/Cargo.lock index 5acb96382bce..ad416228016a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.0.5" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -42,30 +42,23 @@ dependencies = [ ] [[package]] -name = "async-trait" -version = "0.1.73" +name = "arrayvec" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.32", + "serde", ] [[package]] -name = "async-tungstenite" -version = "0.17.2" +name = "async-trait" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ - "futures-io", - "futures-util", - "log", - "pin-project-lite", - "tokio", - "tokio-rustls 0.23.4", - "tungstenite", - "webpki-roots 0.22.6", + "proc-macro2", + "quote", + "syn 2.0.39", ] [[package]] @@ -91,15 +84,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.1" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bitflags" @@ -109,9 +96,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "block-buffer" @@ -124,15 +111,21 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.13.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + +[[package]] +name = "bytecount" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e5f035d16fc623ae5f74981db80a439803888314e3a555fd6f04acd51a3205" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" @@ -140,6 +133,37 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34637b3140142bdf929fb439e8aa4ebad7651ebf7b1080b3930aa16ac1459ff" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] + [[package]] name = "cc" version = "1.0.83" @@ -157,9 +181,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.30" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defd4e7873dbddba6c7c91e199c7fcb946abc4a6a4ac3195400bcfb01b5de877" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ "android-tzdata", "iana-time-zone", @@ -168,6 +192,16 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -176,9 +210,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] @@ -192,6 +226,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -223,7 +276,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.32", + "syn 2.0.39", ] [[package]] @@ -234,7 +287,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" dependencies = [ "darling_core", "quote", - "syn 2.0.32", + "syn 2.0.39", ] [[package]] @@ -244,19 +297,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.0", + "hashbrown", "lock_api", "once_cell", "parking_lot_core", "serde", ] +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + [[package]] name = "deranged" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ + "powerfmt", "serde", ] @@ -289,7 +349,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.39", ] [[package]] @@ -303,9 +363,9 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" dependencies = [ "humantime", "is-terminal", @@ -314,32 +374,42 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" -version = "0.3.3" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" +checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" dependencies = [ - "errno-dragonfly", "libc", "windows-sys", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "error-chain" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" dependencies = [ - "cc", - "libc", + "version_check", ] +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + [[package]] name = "flate2" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" dependencies = [ "crc32fast", "miniz_oxide", @@ -366,7 +436,7 @@ dependencies = [ "intl-memoizer", "intl_pluralrules", "rustc-hash", - "self_cell", + "self_cell 0.10.3", "smallvec", "unic-langid", ] @@ -397,18 +467,18 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -420,9 +490,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -430,44 +500,44 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.39", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -481,6 +551,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -493,9 +572,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", @@ -504,15 +583,21 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "glob" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.3.21" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" dependencies = [ "bytes", "fnv", @@ -529,27 +614,21 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" [[package]] name = "hermit-abi" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "http" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" dependencies = [ "bytes", "fnv", @@ -602,7 +681,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -611,30 +690,30 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http", "hyper", - "rustls 0.21.7", + "rustls", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", ] [[package]] name = "iana-time-zone" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows", + "windows-core", ] [[package]] @@ -654,9 +733,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -664,12 +743,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.3" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "equivalent", + "hashbrown", ] [[package]] @@ -693,9 +772,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.8.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is-terminal" @@ -716,30 +795,30 @@ checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.147" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] name = "linux-raw-sys" -version = "0.4.7" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -753,9 +832,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.6.3" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f232d6ef707e1956a43342693d2a31e72989554d58299d7a88738cc95b0d35c" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "mime" @@ -773,6 +852,21 @@ dependencies = [ "unicase", ] +[[package]] +name = "mini-moka" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e0b72e7c9042467008b10279fc732326bd605459ae03bda88825909dd19b56" +dependencies = [ + "crossbeam-channel", + "crossbeam-utils", + "dashmap", + "skeptic", + "smallvec", + "tagptr", + "triomphe", +] + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -784,9 +878,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", "wasi", @@ -795,9 +889,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] @@ -827,15 +921,6 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" -[[package]] -name = "ordered-float" -version = "2.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" -dependencies = [ - "num-traits", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -848,9 +933,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", "libc", @@ -861,9 +946,9 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" @@ -891,7 +976,6 @@ dependencies = [ "futures-util", "intl-memoizer", "log", - "once_cell", "parking_lot", "poise_macros", "rand", @@ -907,9 +991,15 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.39", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -918,13 +1008,24 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] +[[package]] +name = "pulldown-cmark" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" +dependencies = [ + "bitflags 1.3.2", + "memchr", + "unicase", +] + [[package]] name = "quote" version = "1.0.33" @@ -966,18 +1067,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" -version = "1.9.5" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "697061221ea1b4a94a624f67d0ae2bfe4e22b8a17b6a192afb11046542cc8c47" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", @@ -987,9 +1088,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.8" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2f401f4955220693b56f8ec66ee9c78abffd8d1c4f23dc41a23839eb88f0795" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", @@ -998,17 +1099,17 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64 0.21.4", + "base64", "bytes", "encoding_rs", "futures-core", @@ -1026,13 +1127,14 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.7", + "rustls", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", "tokio-util", "tower-service", "url", @@ -1040,23 +1142,22 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.25.2", + "webpki-roots", "winreg", ] [[package]] name = "ring" -version = "0.16.20" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" dependencies = [ "cc", + "getrandom", "libc", - "once_cell", "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys", ] [[package]] @@ -1073,11 +1174,11 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.13" +version = "0.38.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" +checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", @@ -1086,21 +1187,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" -dependencies = [ - "log", - "ring", - "sct", - "webpki", -] - -[[package]] -name = "rustls" -version = "0.21.7" +version = "0.21.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "629648aced5775d558af50b2b4c7b02983a04b312126d45eeead26e7caa498b9" dependencies = [ "log", "ring", @@ -1110,35 +1199,38 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" dependencies = [ - "base64 0.21.4", + "base64", ] [[package]] name = "rustls-webpki" -version = "0.101.5" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ "ring", "untrusted", ] -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -1147,55 +1239,73 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ "ring", "untrusted", ] [[package]] -name = "self_cell" -version = "0.10.2" +name = "secrecy" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ef965a420fe14fdac7dd018862966a4c14094f900e1650bbc71ddd7d580c8af" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", +] [[package]] -name = "serde" -version = "1.0.188" +name = "self_cell" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" dependencies = [ - "serde_derive", + "self_cell 1.0.2", ] [[package]] -name = "serde-value" -version = "0.7.0" +name = "self_cell" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e388332cd64eb80cd595a00941baf513caffae8dce9cfd0467fc9c66397dade6" + +[[package]] +name = "semver" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" dependencies = [ - "ordered-float", "serde", ] +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.39", ] [[package]] name = "serde_json" -version = "1.0.106" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1216,47 +1326,62 @@ dependencies = [ [[package]] name = "serenity" -version = "0.11.6" +version = "0.12.0-rc2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d007dc45584ecc47e791f2a9a7cf17bf98ac386728106f111159c846d624be3f" +checksum = "09c1bdb27b0d6631216aee3a0cb274637fab79c94cc25caf29005d49b1898779" dependencies = [ + "arrayvec", "async-trait", - "async-tungstenite", - "base64 0.13.1", - "bitflags 1.3.2", + "base64", + "bitflags 2.4.1", "bytes", - "cfg-if", "chrono", "dashmap", "flate2", "futures", - "mime", + "fxhash", "mime_guess", "parking_lot", "percent-encoding", "reqwest", - "rustversion", + "secrecy", "serde", - "serde-value", "serde_json", "time", "tokio", + "tokio-tungstenite", "tracing", "typemap_rev", + "typesize", "url", ] [[package]] -name = "sha-1" -version = "0.10.1" +name = "sha1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] +[[package]] +name = "skeptic" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +dependencies = [ + "bytecount", + "cargo_metadata", + "error-chain", + "glob", + "pulldown-cmark", + "tempfile", + "walkdir", +] + [[package]] name = "slab" version = "0.4.9" @@ -1268,15 +1393,15 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.0" +version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -1284,9 +1409,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys", @@ -1294,9 +1419,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "strsim" @@ -1317,52 +1442,93 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.32" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys", +] + [[package]] name = "termcolor" -version = "1.2.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449" dependencies = [ "winapi-util", ] [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.39", ] [[package]] name = "time" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -1370,24 +1536,24 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] [[package]] name = "tinystr" -version = "0.7.1" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ac3f5b6856e931e15e07b478e98c8045239829a65f9156d4fa7e7788197a5ef" +checksum = "83c02bf3c538ab32ba913408224323915f4ef9a6d61c0e85d493f355921c0ece" dependencies = [ "displaydoc", ] @@ -1409,9 +1575,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.32.0" +version = "1.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9" +checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" dependencies = [ "backtrace", "bytes", @@ -1419,48 +1585,52 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", - "socket2 0.5.4", + "socket2 0.5.5", "tokio-macros", "windows-sys", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.39", ] [[package]] name = "tokio-rustls" -version = "0.23.4" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.20.9", + "rustls", "tokio", - "webpki", ] [[package]] -name = "tokio-rustls" -version = "0.24.1" +name = "tokio-tungstenite" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ - "rustls 0.21.7", + "futures-util", + "log", + "rustls", "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots", ] [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -1478,11 +1648,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -1491,24 +1660,30 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.39", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] +[[package]] +name = "triomphe" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee8098afad3fb0c54a9007aab6804558410503ad676d4633f9c2559a00ac0f" + [[package]] name = "try-lock" version = "0.2.4" @@ -1517,23 +1692,22 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" dependencies = [ - "base64 0.13.1", "byteorder", "bytes", + "data-encoding", "http", "httparse", "log", "rand", - "rustls 0.20.9", - "sha-1", + "rustls", + "sha1", "thiserror", "url", "utf-8", - "webpki", ] [[package]] @@ -1547,15 +1721,44 @@ dependencies = [ [[package]] name = "typemap_rev" -version = "0.1.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" +checksum = "74b08b0c1257381af16a5c3605254d529d3e7e109f3c62befc5d168968192998" [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "typesize" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "3e43a952445d2d9df648a822545093c01699df209ceda4df5ac78fef8969c61c" +dependencies = [ + "chrono", + "dashmap", + "hashbrown", + "mini-moka", + "parking_lot", + "secrecy", + "serde_json", + "time", + "typesize-derive", + "url", +] + +[[package]] +name = "typesize-derive" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5274e4d582fd16b83bf7949cc44d6610d3b0290e441d9e5c337fdda9a003849f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] [[package]] name = "unic-langid" @@ -1592,9 +1795,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" @@ -1607,15 +1810,15 @@ dependencies = [ [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -1635,6 +1838,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -1652,9 +1865,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1662,24 +1875,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.39", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" dependencies = [ "cfg-if", "js-sys", @@ -1689,9 +1902,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1699,22 +1912,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.87" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "wasm-streams" @@ -1731,38 +1944,19 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.64" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" +checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" dependencies = [ "js-sys", "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0e74f82d49d545ad128049b7e88f6576df2da6b02e9ce565c6f533be576957e" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "webpki-roots" -version = "0.22.6" +version = "0.25.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] - -[[package]] -name = "webpki-roots" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" +checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" [[package]] name = "winapi" @@ -1782,9 +1976,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -1796,10 +1990,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows" -version = "0.48.0" +name = "windows-core" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" dependencies = [ "windows-targets", ] @@ -1879,3 +2073,9 @@ dependencies = [ "cfg-if", "windows-sys", ] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/Cargo.toml b/Cargo.toml index ce7d12f1d9f2..af825c7c66db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ repository = "https://github.com/serenity-rs/poise/" tokio = { version = "1.25.1", default-features = false } # for async in general futures-core = { version = "0.3.13", default-features = false } # for async in general futures-util = { version = "0.3.13", default-features = false } # for async in general -once_cell = { version = "1.7.2", default-features = false, features = ["std"] } # to store and set user data poise_macros = { path = "macros", version = "0.5.7" } # remember to update the version on changes! async-trait = { version = "0.1.48", default-features = false } # various traits regex = { version = "1.6.0", default-features = false, features = ["std"] } # prefix @@ -22,8 +21,8 @@ parking_lot = "0.12.1" [dependencies.serenity] default-features = false -features = ["builder", "client", "gateway", "model", "utils", "collector"] -version = "0.11.5" +features = ["builder", "client", "gateway", "model", "utils", "collector", "framework"] +version = "0.12.0-rc2" [dev-dependencies] # For the examples @@ -39,7 +38,6 @@ rand = "0.8.5" default = ["serenity/rustls_backend", "cache", "chrono", "handle_panics"] chrono = ["serenity/chrono"] cache = ["serenity/cache"] -time = ["serenity/time"] # No-op feature because serenity/collector is now enabled by default collector = [] # Enables support for handling panics inside commands via FrameworkError::CommandPanic. diff --git a/examples/advanced_cooldowns/main.rs b/examples/advanced_cooldowns/main.rs index 0b3fe3c09ac1..d21c4fda208e 100644 --- a/examples/advanced_cooldowns/main.rs +++ b/examples/advanced_cooldowns/main.rs @@ -11,7 +11,7 @@ async fn dynamic_cooldowns(ctx: Context<'_>) -> Result<(), Error> { // You can change the cooldown duration depending on the message author, for example let mut cooldown_durations = poise::CooldownConfig::default(); - if ctx.author().id.0 == 472029906943868929 { + if ctx.author().id == 472029906943868929 { cooldown_durations.user = Some(std::time::Duration::from_secs(10)); } @@ -29,6 +29,7 @@ async fn dynamic_cooldowns(ctx: Context<'_>) -> Result<(), Error> { #[tokio::main] async fn main() { + let token = std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN"); let framework = poise::Framework::builder() .options(poise::FrameworkOptions { commands: vec![dynamic_cooldowns()], @@ -37,14 +38,17 @@ async fn main() { manual_cooldowns: true, ..Default::default() }) - .token(std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN")) - .intents(serenity::GatewayIntents::non_privileged()) .setup(|ctx, _ready, framework| { Box::pin(async move { poise::builtins::register_globally(ctx, &framework.options().commands).await?; Ok(Data {}) }) - }); + }) + .build(); + + let client = serenity::Client::builder(token, serenity::GatewayIntents::non_privileged()) + .framework(framework) + .await; - framework.run().await.unwrap(); + client.unwrap().start().await.unwrap(); } diff --git a/examples/basic_structure/main.rs b/examples/basic_structure/main.rs index b18da0609997..af421a94c70b 100644 --- a/examples/basic_structure/main.rs +++ b/examples/basic_structure/main.rs @@ -3,7 +3,12 @@ mod commands; use poise::serenity_prelude as serenity; -use std::{collections::HashMap, env::var, sync::Mutex, time::Duration}; +use std::{ + collections::HashMap, + env::var, + sync::{Arc, Mutex}, + time::Duration, +}; // Types used by all command functions type Error = Box; @@ -41,7 +46,9 @@ async fn main() { commands: vec![commands::help(), commands::vote(), commands::getvotes()], prefix_options: poise::PrefixFrameworkOptions { prefix: Some("~".into()), - edit_tracker: Some(poise::EditTracker::for_timespan(Duration::from_secs(3600))), + edit_tracker: Some(Arc::new(poise::EditTracker::for_timespan( + Duration::from_secs(3600), + ))), additional_prefixes: vec![ poise::Prefix::Literal("hey bot"), poise::Prefix::Literal("hey bot,"), @@ -74,20 +81,19 @@ async fn main() { // Enforce command checks even for owners (enforced by default) // Set to true to bypass checks, which is useful for testing skip_checks_for_owners: false, - event_handler: |_ctx, event, _framework, _data| { + event_handler: |event, _framework, _data| { Box::pin(async move { - println!("Got an event in event handler: {:?}", event.name()); + println!( + "Got an event in event handler: {:?}", + event.snake_case_name() + ); Ok(()) }) }, ..Default::default() }; - poise::Framework::builder() - .token( - var("DISCORD_TOKEN") - .expect("Missing `DISCORD_TOKEN` env var, see README for more information."), - ) + let framework = poise::Framework::builder() .setup(move |ctx, _ready, framework| { Box::pin(async move { println!("Logged in as {}", _ready.user.name); @@ -98,10 +104,16 @@ async fn main() { }) }) .options(options) - .intents( - serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT, - ) - .run() - .await - .unwrap(); + .build(); + + let token = var("DISCORD_TOKEN") + .expect("Missing `DISCORD_TOKEN` env var, see README for more information."); + let intents = + serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT; + + let client = serenity::ClientBuilder::new(token, intents) + .framework(framework) + .await; + + client.unwrap().start().await.unwrap() } diff --git a/examples/event_handler/main.rs b/examples/event_handler/main.rs index 4ca5b3891139..b46cdde789b3 100644 --- a/examples/event_handler/main.rs +++ b/examples/event_handler/main.rs @@ -2,7 +2,6 @@ use std::env::var; use std::sync::atomic::{AtomicU32, Ordering}; use poise::serenity_prelude as serenity; -use poise::Event; // Types used by all command functions type Error = Box; @@ -18,18 +17,12 @@ pub struct Data { async fn main() { env_logger::init(); - let options = poise::FrameworkOptions { - event_handler: |_ctx, event, _framework, _data| { - Box::pin(event_handler(_ctx, event, _framework, _data)) - }, - ..Default::default() - }; + let token = var("DISCORD_TOKEN") + .expect("Missing `DISCORD_TOKEN` env var, see README for more information."); + let intents = + serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT; - poise::Framework::builder() - .token( - var("DISCORD_TOKEN") - .expect("Missing `DISCORD_TOKEN` env var, see README for more information."), - ) + let framework = poise::Framework::builder() .setup(move |_ctx, _ready, _framework| { Box::pin(async move { Ok(Data { @@ -37,26 +30,29 @@ async fn main() { }) }) }) - .options(options) - .intents( - serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT, - ) - .run() - .await - .unwrap(); + .options(poise::FrameworkOptions { + event_handler: |event, framework, data| Box::pin(event_handler(event, framework, data)), + ..Default::default() + }) + .build(); + + let client = serenity::ClientBuilder::new(token, intents) + .framework(framework) + .await; + + client.unwrap().start().await.unwrap(); } async fn event_handler( - ctx: &serenity::Context, - event: &Event<'_>, + event: &serenity::FullEvent, _framework: poise::FrameworkContext<'_, Data, Error>, data: &Data, ) -> Result<(), Error> { match event { - Event::Ready { data_about_bot } => { + serenity::FullEvent::Ready { data_about_bot, .. } => { println!("Logged in as {}", data_about_bot.user.name); } - Event::Message { new_message } => { + serenity::FullEvent::Message { ctx, new_message } => { if new_message.content.to_lowercase().contains("poise") { let mentions = data.poise_mentions.load(Ordering::SeqCst) + 1; data.poise_mentions.store(mentions, Ordering::SeqCst); diff --git a/examples/feature_showcase/attachment_parameter.rs b/examples/feature_showcase/attachment_parameter.rs index 2f65cc4baeb6..038f5e52f1ec 100644 --- a/examples/feature_showcase/attachment_parameter.rs +++ b/examples/feature_showcase/attachment_parameter.rs @@ -22,7 +22,7 @@ pub async fn totalsize( ctx: Context<'_>, #[description = "File to rename"] files: Vec, ) -> Result<(), Error> { - let total = files.iter().map(|f| f.size).sum::(); + let total = files.iter().map(|f| f.size as u64).sum::(); ctx.say(format!( "Total file size: `{}B`. Average size: `{}B`", diff --git a/examples/feature_showcase/autocomplete.rs b/examples/feature_showcase/autocomplete.rs index 1153bb8983d0..1997686a01b0 100644 --- a/examples/feature_showcase/autocomplete.rs +++ b/examples/feature_showcase/autocomplete.rs @@ -1,7 +1,10 @@ -use crate::{Context, Error}; use futures::{Stream, StreamExt}; use std::fmt::Write as _; +use poise::serenity_prelude as serenity; + +use crate::{Context, Error}; + // Poise supports autocomplete on slash command parameters. You need to provide an autocomplete // function, which will be called on demand when the user is typing a command. // @@ -12,7 +15,7 @@ use std::fmt::Write as _; // IntoIterator like Vec and [T; N]. // // The returned collection type must be a &str/String (or number, if you're implementing -// autocomplete on a number type). Wrap the type in poise::AutocompleteChoice to set a custom label +// autocomplete on a number type). Wrap the type in serenity::AutocompleteChoice to set a custom label // for each choice which will be displayed in the Discord UI. // // Example function return types (assuming non-number parameter -> autocomplete choices are string): @@ -20,7 +23,7 @@ use std::fmt::Write as _; // - `-> Vec` // - `-> impl Iterator` // - `-> impl Iterator<&str>` -// - `-> impl Iterator>` +// - `-> impl Iterator async fn autocomplete_name<'a>( _ctx: Context<'_>, @@ -34,10 +37,10 @@ async fn autocomplete_name<'a>( async fn autocomplete_number( _ctx: Context<'_>, _partial: &str, -) -> impl Iterator> { +) -> impl Iterator { // Dummy choices [1_u32, 2, 3, 4, 5].iter().map(|&n| { - poise::AutocompleteChoice::new_with_value( + serenity::AutocompleteChoice::new( format!("{n} (why did discord even give autocomplete choices separate labels)"), n, ) diff --git a/examples/feature_showcase/checks.rs b/examples/feature_showcase/checks.rs index 77dc5bf84096..87adef8be85d 100644 --- a/examples/feature_showcase/checks.rs +++ b/examples/feature_showcase/checks.rs @@ -12,12 +12,7 @@ use poise::serenity_prelude as serenity; #[poise::command(prefix_command, owners_only, hide_in_help)] pub async fn shutdown(ctx: Context<'_>) -> Result<(), Error> { - ctx.framework() - .shard_manager() - .lock() - .await - .shutdown_all() - .await; + ctx.framework().shard_manager().shutdown_all().await; Ok(()) } diff --git a/examples/feature_showcase/collector.rs b/examples/feature_showcase/collector.rs index 2811e8878854..28c18d012191 100644 --- a/examples/feature_showcase/collector.rs +++ b/examples/feature_showcase/collector.rs @@ -7,14 +7,11 @@ pub async fn boop(ctx: Context<'_>) -> Result<(), Error> { let uuid_boop = ctx.id(); let reply = { - let mut components = serenity::CreateComponents::default(); - components.create_action_row(|ar| { - ar.create_button(|b| { - b.style(serenity::ButtonStyle::Primary) - .label("Boop me!") - .custom_id(uuid_boop) - }) - }); + let components = vec![serenity::CreateActionRow::Buttons(vec![ + serenity::CreateButton::new(format!("{uuid_boop}")) + .style(serenity::ButtonStyle::Primary) + .label("Boop me!"), + ])]; CreateReply::default() .content("I want some boops!") @@ -24,7 +21,7 @@ pub async fn boop(ctx: Context<'_>) -> Result<(), Error> { ctx.send(reply).await?; let mut boop_count = 0; - while let Some(mci) = serenity::CollectComponentInteraction::new(ctx) + while let Some(mci) = serenity::ComponentInteractionCollector::new(ctx) .author_id(ctx.author().id) .channel_id(ctx.channel_id()) .timeout(std::time::Duration::from_secs(120)) @@ -34,13 +31,14 @@ pub async fn boop(ctx: Context<'_>) -> Result<(), Error> { boop_count += 1; let mut msg = mci.message.clone(); - msg.edit(ctx, |m| m.content(format!("Boop count: {}", boop_count))) - .await?; - - mci.create_interaction_response(ctx, |ir| { - ir.kind(serenity::InteractionResponseType::DeferredUpdateMessage) - }) + msg.edit( + ctx, + serenity::EditMessage::new().content(format!("Boop count: {boop_count}")), + ) .await?; + + mci.create_response(ctx, serenity::CreateInteractionResponse::Acknowledge) + .await?; } Ok(()) diff --git a/examples/feature_showcase/localization.rs b/examples/feature_showcase/localization.rs index 3616d73efadd..2556c6705323 100644 --- a/examples/feature_showcase/localization.rs +++ b/examples/feature_showcase/localization.rs @@ -60,6 +60,6 @@ pub async fn welcome( let message = message .localized_name(ctx.locale().unwrap_or("")) .unwrap_or_else(|| message.name()); - ctx.say(format!("<@{}> {}", user.id.0, message)).await?; + ctx.say(format!("<@{}> {}", user.id, message)).await?; Ok(()) } diff --git a/examples/feature_showcase/main.rs b/examples/feature_showcase/main.rs index 2e955652e426..34ef67041659 100644 --- a/examples/feature_showcase/main.rs +++ b/examples/feature_showcase/main.rs @@ -91,16 +91,21 @@ async fn main() { }, ..Default::default() }) - .token(std::env::var("DISCORD_TOKEN").unwrap()) - .intents( - serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT, - ) .setup(move |ctx, _ready, framework| { Box::pin(async move { poise::builtins::register_globally(ctx, &framework.options().commands).await?; Ok(Data {}) }) - }); + }) + .build(); + + let token = std::env::var("DISCORD_TOKEN").unwrap(); + let intents = + serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT; + + let client = serenity::ClientBuilder::new(token, intents) + .framework(framework) + .await; - framework.run().await.unwrap(); + client.unwrap().start().await.unwrap() } diff --git a/examples/feature_showcase/modal.rs b/examples/feature_showcase/modal.rs index 8bcaecb72adf..32be2503f150 100644 --- a/examples/feature_showcase/modal.rs +++ b/examples/feature_showcase/modal.rs @@ -23,15 +23,12 @@ pub async fn modal(ctx: poise::ApplicationContext<'_, Data, Error>) -> Result<() /// present. #[poise::command(prefix_command, slash_command)] pub async fn component_modal(ctx: crate::Context<'_>) -> Result<(), Error> { - let mut components = serenity::CreateComponents::default(); let reply = { - components.create_action_row(|a| { - a.create_button(|b| { - b.custom_id("open_modal") - .label("Open modal") - .style(poise::serenity_prelude::ButtonStyle::Success) - }) - }); + let components = vec![serenity::CreateActionRow::Buttons(vec![ + serenity::CreateButton::new("open_modal") + .label("Open modal") + .style(poise::serenity_prelude::ButtonStyle::Success), + ])]; poise::CreateReply::default() .content("Click the button below to open the modal") @@ -40,11 +37,10 @@ pub async fn component_modal(ctx: crate::Context<'_>) -> Result<(), Error> { ctx.send(reply).await?; - while let Some(mci) = - poise::serenity_prelude::CollectComponentInteraction::new(ctx.serenity_context()) - .timeout(std::time::Duration::from_secs(120)) - .filter(move |mci| mci.data.custom_id == "open_modal") - .await + while let Some(mci) = serenity::ModalInteractionCollector::new(ctx.serenity_context()) + .timeout(std::time::Duration::from_secs(120)) + .filter(move |mci| mci.data.custom_id == "open_modal") + .await { let data = poise::execute_modal_on_component_interaction::(ctx, mci, None, None).await?; diff --git a/examples/feature_showcase/parameter_attributes.rs b/examples/feature_showcase/parameter_attributes.rs index 88f08d8877ad..6cd78a73d576 100644 --- a/examples/feature_showcase/parameter_attributes.rs +++ b/examples/feature_showcase/parameter_attributes.rs @@ -39,9 +39,7 @@ pub async fn voiceinfo( channel.bitrate.unwrap_or_default(), channel.user_limit.unwrap_or_default(), channel.rtc_region.unwrap_or_default(), - channel - .video_quality_mode - .unwrap_or(serenity::VideoQualityMode::Unknown) + channel.video_quality_mode ); ctx.say(response).await?; diff --git a/examples/feature_showcase/track_edits.rs b/examples/feature_showcase/track_edits.rs index 56d931f37806..51eab90cd270 100644 --- a/examples/feature_showcase/track_edits.rs +++ b/examples/feature_showcase/track_edits.rs @@ -6,17 +6,15 @@ pub async fn test_reuse_response(ctx: Context<'_>) -> Result<(), Error> { let image_url = "https://raw.githubusercontent.com/serenity-rs/serenity/current/logo.png"; let reply = { - let mut embed = serenity::CreateEmbed::default(); - embed.description("embed 1").image(image_url); + let embed = serenity::CreateEmbed::default() + .description("embed 1") + .image(image_url); - let mut components = serenity::CreateComponents::default(); - components.create_action_row(|b| { - b.create_button(|b| { - b.label("button 1") - .style(serenity::ButtonStyle::Primary) - .custom_id(1) - }) - }); + let components = vec![serenity::CreateActionRow::Buttons(vec![ + serenity::CreateButton::new("1") + .label("button 1") + .style(serenity::ButtonStyle::Primary), + ])]; poise::CreateReply::default() .content("message 1") @@ -30,17 +28,15 @@ pub async fn test_reuse_response(ctx: Context<'_>) -> Result<(), Error> { let image_url = "https://raw.githubusercontent.com/serenity-rs/serenity/current/examples/e09_create_message_builder/ferris_eyes.png"; let reply = { - let mut embed = serenity::CreateEmbed::default(); - embed.description("embed 2").image(image_url); - - let mut components = serenity::CreateComponents::default(); - components.create_action_row(|b| { - b.create_button(|b| { - b.label("button 2") - .style(serenity::ButtonStyle::Danger) - .custom_id(2) - }) - }); + let embed = serenity::CreateEmbed::default() + .description("embed 2") + .image(image_url); + + let components = vec![serenity::CreateActionRow::Buttons(vec![ + serenity::CreateButton::new("2") + .label("button 2") + .style(serenity::ButtonStyle::Danger), + ])]; poise::CreateReply::default() .content("message 2") diff --git a/examples/fluent_localization/main.rs b/examples/fluent_localization/main.rs index e1b7724c4227..f215a3a99584 100644 --- a/examples/fluent_localization/main.rs +++ b/examples/fluent_localization/main.rs @@ -26,7 +26,7 @@ pub async fn welcome( ) -> Result<(), Error> { use poise::ChoiceParameter as _; - ctx.say(format!("<@{}> {}", user.id.0, tr!(ctx, message.name()))) + ctx.say(format!("<@{}> {}", user.id, tr!(ctx, message.name()))) .await?; Ok(()) } @@ -64,15 +64,20 @@ async fn main() { let translations = translation::read_ftl().expect("failed to read translation files"); translation::apply_translations(&translations, &mut commands); - poise::Framework::builder() - .token(std::env::var("TOKEN").unwrap()) - .intents(serenity::GatewayIntents::non_privileged()) + let token = std::env::var("TOKEN").unwrap(); + let intents = serenity::GatewayIntents::non_privileged(); + + let framework = poise::Framework::builder() .options(poise::FrameworkOptions { commands, ..Default::default() }) .setup(move |_, _, _| Box::pin(async move { Ok(Data { translations }) })) - .run() - .await - .unwrap(); + .build(); + + let client = serenity::ClientBuilder::new(token, intents) + .framework(framework) + .await; + + client.unwrap().start().await.unwrap() } diff --git a/examples/help_generation/main.rs b/examples/help_generation/main.rs index 074fca1ce2c9..756372918930 100644 --- a/examples/help_generation/main.rs +++ b/examples/help_generation/main.rs @@ -317,6 +317,10 @@ You can edit your `?help` message to the bot and the bot will edit its response. #[tokio::main] async fn main() { + let token = std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN"); + let intents = + serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT; + let framework = poise::Framework::builder() .options(poise::FrameworkOptions { commands: vec![ @@ -338,16 +342,17 @@ async fn main() { }, ..Default::default() }) - .token(std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN")) - .intents( - serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT, - ) .setup(|ctx, _ready, framework| { Box::pin(async move { poise::builtins::register_globally(ctx, &framework.options().commands).await?; Ok(Data {}) }) - }); + }) + .build(); + + let client = serenity::ClientBuilder::new(token, intents) + .framework(framework) + .await; - framework.run().await.unwrap(); + client.unwrap().start().await.unwrap(); } diff --git a/examples/invocation_data/main.rs b/examples/invocation_data/main.rs index 9bef5c5ec232..21c18e08600f 100644 --- a/examples/invocation_data/main.rs +++ b/examples/invocation_data/main.rs @@ -4,6 +4,8 @@ //! This module has a test command which stores a dummy payload to check that this string is //! available in all phases of command execution +use poise::serenity_prelude as serenity; + type Error = &'static str; type Context<'a> = poise::Context<'a, (), Error>; @@ -16,7 +18,7 @@ async fn my_check(ctx: Context<'_>) -> Result { Ok(true) } -async fn my_autocomplete(ctx: Context<'_>, _: &str) -> impl Iterator { +async fn my_autocomplete(ctx: Context<'_>, _: &str) -> impl Iterator { println!( "In autocomplete: {:?}", ctx.invocation_data::<&str>().await.as_deref() @@ -47,17 +49,18 @@ pub async fn invocation_data_test( #[tokio::main] async fn main() { - poise::Framework::builder() - .token(std::env::var("TOKEN").unwrap()) + let token = std::env::var("TOKEN").unwrap(); + let intents = + serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT; + + let framework = poise::Framework::builder() .setup(move |ctx, _, framework| { Box::pin(async move { - poise::serenity_prelude::GuildId(703332075914264606) - .set_application_commands(ctx, |b| { - *b = poise::builtins::create_application_commands( - &framework.options().commands, - ); - b - }) + let commands = + poise::builtins::create_application_commands(&framework.options().commands); + + serenity::GuildId::new(703332075914264606) + .set_commands(ctx, commands) .await .unwrap(); Ok(()) @@ -116,7 +119,11 @@ async fn main() { }, ..Default::default() }) - .run() - .await - .unwrap(); + .build(); + + let client = serenity::ClientBuilder::new(token, intents) + .framework(framework) + .await; + + client.unwrap().start().await.unwrap(); } diff --git a/examples/manual_dispatch/main.rs b/examples/manual_dispatch/main.rs index e954604f0027..0a11fc1de781 100644 --- a/examples/manual_dispatch/main.rs +++ b/examples/manual_dispatch/main.rs @@ -16,8 +16,7 @@ async fn ping(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> { struct Handler { options: poise::FrameworkOptions<(), Error>, - shard_manager: - std::sync::Mutex>>>, + shard_manager: std::sync::Mutex>>, } #[serenity::async_trait] impl serenity::EventHandler for Handler { @@ -25,13 +24,14 @@ impl serenity::EventHandler for Handler { // FrameworkContext contains all data that poise::Framework usually manages let shard_manager = (*self.shard_manager.lock().unwrap()).clone().unwrap(); let framework_data = poise::FrameworkContext { - bot_id: serenity::UserId(846453852164587620), + bot_id: serenity::UserId::new(846453852164587620), options: &self.options, user_data: &(), shard_manager: &shard_manager, }; - poise::dispatch_event(framework_data, &ctx, &poise::Event::Message { new_message }).await; + let event = serenity::FullEvent::Message { ctx, new_message }; + poise::dispatch_event(framework_data, event).await; } // For slash commands or edit tracking to work, forward interaction_create and message_update @@ -54,6 +54,7 @@ async fn main() -> Result<(), Error> { let mut client = serenity::Client::builder(token, intents) .event_handler_arc(handler.clone()) .await?; + *handler.shard_manager.lock().unwrap() = Some(client.shard_manager.clone()); client.start().await?; diff --git a/examples/quickstart/main.rs b/examples/quickstart/main.rs index f32864a9cf7b..ea2220999751 100644 --- a/examples/quickstart/main.rs +++ b/examples/quickstart/main.rs @@ -18,19 +18,24 @@ async fn age( #[tokio::main] async fn main() { + let token = std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN"); + let intents = serenity::GatewayIntents::non_privileged(); + let framework = poise::Framework::builder() .options(poise::FrameworkOptions { commands: vec![age()], ..Default::default() }) - .token(std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN")) - .intents(serenity::GatewayIntents::non_privileged()) .setup(|ctx, _ready, framework| { Box::pin(async move { poise::builtins::register_globally(ctx, &framework.options().commands).await?; Ok(Data {}) }) - }); + }) + .build(); - framework.run().await.unwrap(); + let client = serenity::ClientBuilder::new(token, intents) + .framework(framework) + .await; + client.unwrap().start().await.unwrap(); } diff --git a/macros/src/command/slash.rs b/macros/src/command/slash.rs index a265fe068bbf..0e22e5edb6a9 100644 --- a/macros/src/command/slash.rs +++ b/macros/src/command/slash.rs @@ -42,21 +42,15 @@ pub fn generate_parameters(inv: &Invocation) -> Result -> AutocompleteChoice - .map(|value| poise::AutocompleteChoice::from(value)) - // AutocompleteChoice -> serde_json::Value - .map(|choice| poise::serenity_prelude::json::json!({ - "name": choice.label, - "value": choice.value, - })) + .map(poise::serenity_prelude::AutocompleteChoice::from) .collect() .await; let mut response = poise::serenity_prelude::CreateAutocompleteResponse::default(); - response.set_choices(poise::serenity_prelude::json::Value::Array(choices_json)); - Ok(response) + Ok(response.set_choices(choices_vec)) })) } } None => quote::quote! { None }, @@ -65,25 +59,25 @@ pub fn generate_parameters(inv: &Invocation) -> Result quote::quote! { o.min_number_value(#x as f64); }, + Some(x) => quote::quote! { .min_number_value(#x as f64) }, None => quote::quote! {}, }; let max_value_setter = match ¶m.args.max { - Some(x) => quote::quote! { o.max_number_value(#x as f64); }, + Some(x) => quote::quote! { .max_number_value(#x as f64) }, None => quote::quote! {}, }; // TODO: move this to poise::CommandParameter::{min_length, max_length} fields let min_length_setter = match ¶m.args.min_length { - Some(x) => quote::quote! { o.min_length(#x); }, + Some(x) => quote::quote! { .min_length(#x) }, None => quote::quote! {}, }; let max_length_setter = match ¶m.args.max_length { - Some(x) => quote::quote! { o.max_length(#x); }, + Some(x) => quote::quote! { .max_length(#x) }, None => quote::quote! {}, }; let type_setter = match inv.args.slash_command { true => quote::quote! { Some(|o| { - poise::create_slash_argument!(#type_, o); + poise::create_slash_argument!(#type_, o) #min_value_setter #max_value_setter #min_length_setter #max_length_setter }) }, diff --git a/macros/src/modal.rs b/macros/src/modal.rs index 866196792462..597e4d402d61 100644 --- a/macros/src/modal.rs +++ b/macros/src/modal.rs @@ -15,8 +15,8 @@ struct StructAttributes { struct FieldAttributes { name: Option, placeholder: Option, - min_length: Option, - max_length: Option, + min_length: Option, + max_length: Option, paragraph: Option<()>, } @@ -66,25 +66,21 @@ pub fn modal(input: syn::DeriveInput) -> Result { let min_length = field_attrs.min_length.into_iter(); let max_length = field_attrs.max_length.into_iter(); builders.push(quote::quote! { - .create_action_row(|b| b - .create_input_text(|b| { - if let Some(defaults) = &mut defaults { - // Can use `defaults.#field_ident` directly in Edition 2021 due to more - // specific closure capture rules - let default = std::mem::take(&mut defaults.#field_ident); - // Option::from().unwrap_or_default() dance to handle both T and Option - b.value(Option::from(default).unwrap_or_else(String::new)); - } - b - .label(#label) + serenity::CreateActionRow::InputText({ + let mut b = serenity::CreateInputText::new(#style, #label, stringify!(#field_ident)); + if let Some(defaults) = &mut defaults { + // Can use `defaults.#field_ident` directly in Edition 2021 due to more + // specific closure capture rules + let default = std::mem::take(&mut defaults.#field_ident); + // Option::from().unwrap_or_default() dance to handle both T and Option + b = b.value(Option::from(default).unwrap_or_else(String::new)); + } + b #( .placeholder(#placeholder) )* .required(#required) - .custom_id(stringify!(#field_ident)) - .style(#style) #( .min_length(#min_length) )* #( .max_length(#max_length) )* - }) - ) + }), }); // Create modal parser code for this field @@ -105,16 +101,13 @@ pub fn modal(input: syn::DeriveInput) -> Result { Ok(quote::quote! { const _: () = { use poise::serenity_prelude as serenity; impl #impl_generics poise::Modal for #struct_ident #ty_generics #where_clause { - fn create(mut defaults: Option, custom_id: String) -> serenity::CreateInteractionResponse<'static> { - let mut b = serenity::CreateInteractionResponse::default(); - b.kind(serenity::InteractionResponseType::Modal); - b.interaction_response_data(|b| { - b.custom_id(custom_id).title(#modal_title).components(|b| b #( #builders )* ) - }); - b + fn create(mut defaults: Option, custom_id: String) -> serenity::CreateInteractionResponse { + serenity::CreateInteractionResponse::Modal( + serenity::CreateModal::new(custom_id, #modal_title).components(vec!{#( #builders )*}) + ) } - fn parse(mut data: serenity::ModalSubmitInteractionData) -> ::std::result::Result { + fn parse(mut data: serenity::ModalInteractionData) -> ::std::result::Result { Ok(Self { #( #parsers )* }) } } diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 4766b88b2eb5..fb504667c3d2 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -40,7 +40,7 @@ pub async fn on_error( } crate::FrameworkError::EventHandler { error, event, .. } => log::error!( "User event event handler encountered an error on {} event: {}", - event.name(), + event.snake_case_name(), error ), crate::FrameworkError::Command { ctx, error } => { @@ -64,8 +64,7 @@ pub async fn on_error( } crate::FrameworkError::CommandPanic { ctx, payload: _ } => { // Not showing the payload to the user because it may contain sensitive info - let mut embed = serenity::CreateEmbed::default(); - embed + let embed = serenity::CreateEmbed::default() .title("Internal error") .color((255, 0, 0)) .description("An unexpected internal error has occurred."); @@ -187,10 +186,7 @@ pub async fn on_error( ); } crate::FrameworkError::UnknownInteraction { interaction, .. } => { - log::warn!( - "received unknown interaction \"{}\"", - interaction.data().name - ); + log::warn!("received unknown interaction \"{}\"", interaction.data.name); } crate::FrameworkError::__NonExhaustive(unreachable) => match unreachable {}, } @@ -237,18 +233,13 @@ pub async fn servers(ctx: crate::Context<'_, U, E>) -> Result<(), serenity let mut hidden_guilds_members = 0; let mut shown_guilds = Vec::<(String, u64)>::new(); for guild_id in ctx.cache().guilds() { - match ctx.cache().guild_field(guild_id, |g| { - ( - g.name.clone(), - g.member_count, - g.features.iter().any(|x| x == "DISCOVERABLE"), - ) - }) { - Some((name, member_count, is_public)) => { + match ctx.cache().guild(guild_id) { + Some(guild) => { + let is_public = guild.features.iter().any(|x| x == "DISCOVERABLE"); if !is_public && !show_private_guilds { hidden_guilds += 1; // private guild whose name and size shouldn't be exposed } else { - shown_guilds.push((name, member_count)) + shown_guilds.push((guild.name.clone(), guild.member_count)) } } None => hidden_guilds += 1, // uncached guild diff --git a/src/builtins/paginate.rs b/src/builtins/paginate.rs index b42671a2833e..83094be06b58 100644 --- a/src/builtins/paginate.rs +++ b/src/builtins/paginate.rs @@ -43,25 +43,21 @@ pub async fn paginate( // Send the embed with the first page as content let reply = { - let mut embed = serenity::CreateEmbed::default(); - embed.description(pages[0]); - - let mut components = serenity::CreateComponents::default(); - components.create_action_row(|b| { - b.create_button(|b| b.custom_id(&prev_button_id).emoji('â—€')) - .create_button(|b| b.custom_id(&next_button_id).emoji('â–¶')) - }); + let components = serenity::CreateActionRow::Buttons(vec![ + serenity::CreateButton::new(&prev_button_id).emoji('â—€'), + serenity::CreateButton::new(&next_button_id).emoji('â–¶'), + ]); crate::CreateReply::default() - .embed(embed) - .components(components) + .embed(serenity::CreateEmbed::default().description(pages[0])) + .components(vec![components]) }; ctx.send(reply).await?; // Loop through incoming interactions with the navigation buttons let mut current_page = 0; - while let Some(press) = serenity::CollectComponentInteraction::new(ctx) + while let Some(press) = serenity::collector::ComponentInteractionCollector::new(ctx) // We defined our button IDs to start with `ctx_id`. If they don't, some other command's // button was pressed .filter(move |press| press.data.custom_id.starts_with(&ctx_id.to_string())) @@ -84,10 +80,13 @@ pub async fn paginate( // Update the message with the new page contents press - .create_interaction_response(ctx, |b| { - b.kind(serenity::InteractionResponseType::UpdateMessage) - .interaction_response_data(|b| b.embed(|b| b.description(pages[current_page]))) - }) + .create_response( + ctx.serenity_context(), + serenity::CreateInteractionResponse::UpdateMessage( + serenity::CreateInteractionResponseMessage::new() + .embed(serenity::CreateEmbed::new().description(pages[current_page])), + ), + ) .await?; } diff --git a/src/builtins/register.rs b/src/builtins/register.rs index ca5cc7e7d7ca..d4eb5cb1c0d2 100644 --- a/src/builtins/register.rs +++ b/src/builtins/register.rs @@ -13,35 +13,32 @@ use crate::serenity_prelude as serenity; /// let commands = &ctx.framework().options().commands; /// let create_commands = poise::builtins::create_application_commands(commands); /// -/// serenity::Command::set_global_application_commands(ctx, |b| { -/// *b = create_commands; // replace the given builder with the one prepared by poise -/// b -/// }).await?; +/// serenity::Command::set_global_commands(ctx, create_commands).await?; /// # Ok(()) } /// ``` pub fn create_application_commands( commands: &[crate::Command], -) -> serenity::CreateApplicationCommands { +) -> Vec { /// We decided to extract context menu commands recursively, despite the subcommand hierarchy /// not being preserved. Because it's more confusing to just silently discard context menu /// commands if they're not top-level commands. /// https://discord.com/channels/381880193251409931/919310428344029265/947970605985189989 fn recursively_add_context_menu_commands( - builder: &mut serenity::CreateApplicationCommands, + builder: &mut Vec, command: &crate::Command, ) { if let Some(context_menu_command) = command.create_as_context_menu_command() { - builder.add_application_command(context_menu_command); + builder.push(context_menu_command); } for subcommand in &command.subcommands { recursively_add_context_menu_commands(builder, subcommand); } } - let mut commands_builder = serenity::CreateApplicationCommands::default(); + let mut commands_builder = Vec::with_capacity(commands.len()); for command in commands { if let Some(slash_command) = command.create_as_slash_command() { - commands_builder.add_application_command(slash_command); + commands_builder.push(slash_command); } recursively_add_context_menu_commands(&mut commands_builder, command); } @@ -57,11 +54,7 @@ pub async fn register_globally( commands: &[crate::Command], ) -> Result<(), serenity::Error> { let builder = create_application_commands(commands); - serenity::Command::set_global_application_commands(http, |b| { - *b = builder; - b - }) - .await?; + serenity::Command::set_global_commands(http, builder).await?; Ok(()) } @@ -75,12 +68,7 @@ pub async fn register_in_guild( guild_id: serenity::GuildId, ) -> Result<(), serenity::Error> { let builder = create_application_commands(commands); - guild_id - .set_application_commands(http, |b| { - *b = builder; - b - }) - .await?; + guild_id.set_commands(http, builder).await?; Ok(()) } @@ -108,16 +96,12 @@ pub async fn register_application_commands( } let commands_builder = create_application_commands(&ctx.framework().options().commands); - let num_commands = commands_builder.0.len(); + let num_commands = commands_builder.len(); if global { - ctx.say(format!("Registering {} commands...", num_commands)) + ctx.say(format!("Registering {num_commands} commands...",)) .await?; - serenity::Command::set_global_application_commands(ctx, |b| { - *b = commands_builder; - b - }) - .await?; + serenity::Command::set_global_commands(ctx, commands_builder).await?; } else { let guild_id = match ctx.guild_id() { Some(x) => x, @@ -127,14 +111,9 @@ pub async fn register_application_commands( } }; - ctx.say(format!("Registering {} commands...", num_commands)) - .await?; - guild_id - .set_application_commands(ctx, |b| { - *b = commands_builder; - b - }) + ctx.say(format!("Registering {num_commands} commands...")) .await?; + guild_id.set_commands(ctx, commands_builder).await?; } ctx.say("Done!").await?; @@ -173,7 +152,7 @@ pub async fn register_application_commands_buttons( ctx: crate::Context<'_, U, E>, ) -> Result<(), serenity::Error> { let create_commands = create_application_commands(&ctx.framework().options().commands); - let num_commands = create_commands.0.len(); + let num_commands = create_commands.len(); let is_bot_owner = ctx.framework().options().owners.contains(&ctx.author().id); if !is_bot_owner { @@ -181,40 +160,28 @@ pub async fn register_application_commands_buttons( return Ok(()); } - let mut components = serenity::CreateComponents::default(); - components - .create_action_row(|r| { - r.create_button(|b| { - b.custom_id("register.guild") - .label("Register in guild") - .style(serenity::ButtonStyle::Primary) - .emoji('📋') - }) - .create_button(|b| { - b.custom_id("unregister.guild") - .label("Delete in guild") - .style(serenity::ButtonStyle::Danger) - .emoji('🗑') - }) - }) - .create_action_row(|r| { - r.create_button(|b| { - b.custom_id("register.global") - .label("Register globally") - .style(serenity::ButtonStyle::Primary) - .emoji('📋') - }) - .create_button(|b| { - b.custom_id("unregister.global") - .label("Delete globally") - .style(serenity::ButtonStyle::Danger) - .emoji('🗑') - }) - }); + let components = serenity::CreateActionRow::Buttons(vec![ + serenity::CreateButton::new("register.guild") + .label("Register in guild") + .style(serenity::ButtonStyle::Primary) + .emoji('📋'), + serenity::CreateButton::new("unregister.guild") + .label("Delete in guild") + .style(serenity::ButtonStyle::Danger) + .emoji('🗑'), + serenity::CreateButton::new("register.global") + .label("Register globally") + .style(serenity::ButtonStyle::Primary) + .emoji('📋'), + serenity::CreateButton::new("unregister.global") + .label("Unregister globally") + .style(serenity::ButtonStyle::Danger) + .emoji('🗑'), + ]); let builder = crate::CreateReply::default() .content("Choose what to do with the commands:") - .components(components); + .components(vec![components]); let reply = ctx.send(builder).await?; @@ -229,7 +196,7 @@ pub async fn register_application_commands_buttons( .edit( ctx, crate::CreateReply::default() - .components(serenity::CreateComponents::default()) + .components(vec![]) .content("Processing... Please wait."), ) .await?; // remove buttons after button press and edit message @@ -258,18 +225,13 @@ pub async fn register_application_commands_buttons( if global { if register { ctx.say(format!( - ":gear: Registering {} global commands...", - num_commands + ":gear: Registering {num_commands} global commands...", )) .await?; - serenity::Command::set_global_application_commands(ctx, |b| { - *b = create_commands; - b - }) - .await?; + serenity::Command::set_global_commands(ctx, create_commands).await?; } else { ctx.say(":gear: Unregistering global commands...").await?; - serenity::Command::set_global_application_commands(ctx, |b| b).await?; + serenity::Command::set_global_commands(ctx, vec![]).await?; } } else { let guild_id = match ctx.guild_id() { @@ -281,19 +243,13 @@ pub async fn register_application_commands_buttons( }; if register { ctx.say(format!( - ":gear: Registering {} guild commands...", - num_commands + ":gear: Registering {num_commands} guild commands...", )) .await?; - guild_id - .set_application_commands(ctx, |b| { - *b = create_commands; - b - }) - .await?; + guild_id.set_commands(ctx, create_commands).await?; } else { ctx.say(":gear: Unregistering guild commands...").await?; - guild_id.set_application_commands(ctx, |b| b).await?; + guild_id.set_commands(ctx, vec![]).await?; } } diff --git a/src/choice_parameter.rs b/src/choice_parameter.rs index 4c4687495f28..94b7753494a6 100644 --- a/src/choice_parameter.rs +++ b/src/choice_parameter.rs @@ -26,25 +26,28 @@ pub trait ChoiceParameter: Sized { impl crate::SlashArgument for T { async fn extract( _: &serenity::Context, - _: crate::ApplicationCommandOrAutocompleteInteraction<'_>, - value: &serenity::json::Value, + _: &serenity::CommandInteraction, + value: &serenity::ResolvedValue<'_>, ) -> ::std::result::Result { #[allow(unused_imports)] - use ::serenity::json::prelude::*; // Required for simd-json :| + use ::serenity::json::*; // Required for simd-json :| - let choice_key = value - .as_u64() - .ok_or(crate::SlashArgError::CommandStructureMismatch { - description: "expected u64", - })?; + let choice_key = match value { + serenity::ResolvedValue::Integer(int) => *int as u64, + _ => { + return Err(crate::SlashArgError::CommandStructureMismatch { + description: "expected u64", + }) + } + }; Self::from_index(choice_key as _).ok_or(crate::SlashArgError::CommandStructureMismatch { description: "out of bounds choice key", }) } - fn create(builder: &mut serenity::CreateApplicationCommandOption) { - builder.kind(serenity::CommandOptionType::Integer); + fn create(builder: serenity::CreateCommandOption) -> serenity::CreateCommandOption { + builder.kind(serenity::CommandOptionType::Integer) } fn choices() -> Vec { diff --git a/src/dispatch/common.rs b/src/dispatch/common.rs index 13c2f86d2183..d57fc6e2e5fb 100644 --- a/src/dispatch/common.rs +++ b/src/dispatch/common.rs @@ -31,7 +31,7 @@ async fn user_permissions( let member = guild.member(ctx, user_id).await.ok()?; - guild.user_permissions_in(&channel, &member).ok() + Some(guild.user_permissions_in(&channel, &member)) } /// Retrieves the set of permissions that are lacking, relative to the given required permission set @@ -79,7 +79,7 @@ async fn check_permissions_and_cooldown_single<'a, U, E>( Some(guild_id) => { #[cfg(feature = "cache")] if ctx.framework().options().require_cache_for_guild_check - && ctx.cache().guild_field(guild_id, |_| ()).is_none() + && ctx.cache().guild(guild_id).is_none() { return Err(crate::FrameworkError::GuildOnly { ctx }); } diff --git a/src/dispatch/mod.rs b/src/dispatch/mod.rs index bdd8e809b189..212c3c4b9d09 100644 --- a/src/dispatch/mod.rs +++ b/src/dispatch/mod.rs @@ -21,7 +21,7 @@ pub struct FrameworkContext<'a, U, E> { /// Your provided user data pub user_data: &'a U, /// Serenity shard manager. Can be used for example to shutdown the bot - pub shard_manager: &'a std::sync::Arc>, + pub shard_manager: &'a std::sync::Arc, // deliberately not non exhaustive because you need to create FrameworkContext from scratch // to run your own event loop } @@ -44,7 +44,7 @@ impl<'a, U, E> FrameworkContext<'a, U, E> { /// /// This function exists for API compatiblity with [`crate::Framework`]. On this type, you can /// also just access the public `shard_manager` field. - pub fn shard_manager(&self) -> std::sync::Arc> { + pub fn shard_manager(&self) -> std::sync::Arc { self.shard_manager.clone() } @@ -61,11 +61,10 @@ impl<'a, U, E> FrameworkContext<'a, U, E> { /// Central event handling function of this library pub async fn dispatch_event( framework: crate::FrameworkContext<'_, U, E>, - ctx: &serenity::Context, - event: &crate::Event<'_>, + event: serenity::FullEvent, ) { - match event { - crate::Event::Message { new_message } => { + match &event { + serenity::FullEvent::Message { ctx, new_message } => { let invocation_data = tokio::sync::Mutex::new(Box::new(()) as _); let mut parent_commands = Vec::new(); let trigger = crate::MessageDispatchTrigger::MessageCreate; @@ -82,7 +81,7 @@ pub async fn dispatch_event( error.handle(framework.options).await; } } - crate::Event::MessageUpdate { event, .. } => { + serenity::FullEvent::MessageUpdate { ctx, event, .. } => { if let Some(edit_tracker) = &framework.options.prefix_options.edit_tracker { let msg = edit_tracker.write().unwrap().process_message_update( event, @@ -114,8 +113,10 @@ pub async fn dispatch_event( } } } - crate::Event::MessageDelete { - deleted_message_id, .. + serenity::FullEvent::MessageDelete { + deleted_message_id, + ctx, + .. } => { if let Some(edit_tracker) = &framework.options.prefix_options.edit_tracker { let bot_response = edit_tracker @@ -129,8 +130,9 @@ pub async fn dispatch_event( } } } - crate::Event::InteractionCreate { - interaction: serenity::Interaction::ApplicationCommand(interaction), + serenity::FullEvent::InteractionCreate { + ctx, + interaction: serenity::Interaction::Command(interaction), } => { let invocation_data = tokio::sync::Mutex::new(Box::new(()) as _); let mut parent_commands = Vec::new(); @@ -140,6 +142,7 @@ pub async fn dispatch_event( interaction, &std::sync::atomic::AtomicBool::new(false), &invocation_data, + &interaction.data.options(), &mut parent_commands, ) .await @@ -147,7 +150,8 @@ pub async fn dispatch_event( error.handle(framework.options).await; } } - crate::Event::InteractionCreate { + serenity::FullEvent::InteractionCreate { + ctx, interaction: serenity::Interaction::Autocomplete(interaction), } => { let invocation_data = tokio::sync::Mutex::new(Box::new(()) as _); @@ -158,6 +162,7 @@ pub async fn dispatch_event( interaction, &std::sync::atomic::AtomicBool::new(false), &invocation_data, + &interaction.data.options(), &mut parent_commands, ) .await @@ -171,12 +176,11 @@ pub async fn dispatch_event( // Do this after the framework's Ready handling, so that get_user_data() doesnt // potentially block infinitely if let Err(error) = - (framework.options.event_handler)(ctx, event, framework, framework.user_data).await + (framework.options.event_handler)(&event, framework, framework.user_data).await { let error = crate::FrameworkError::EventHandler { - ctx, error, - event, + event: &event, framework, }; (framework.options.on_error)(error).await; diff --git a/src/dispatch/prefix.rs b/src/dispatch/prefix.rs index 386c8ba00579..aba0177dfee2 100644 --- a/src/dispatch/prefix.rs +++ b/src/dispatch/prefix.rs @@ -91,7 +91,7 @@ async fn strip_prefix<'a, U, E>( msg.content .strip_prefix("<@")? .trim_start_matches('!') - .strip_prefix(&framework.bot_id.0.to_string())? + .strip_prefix(&framework.bot_id.to_string())? .strip_prefix('>') })() { let mention_prefix = &msg.content[..(msg.content.len() - stripped_content.len())]; @@ -234,7 +234,7 @@ pub async fn parse_invocation<'a, U: Send + Sync, E>( } // Check if we can execute commands contained in thread creation messages - if msg.kind == serenity::channel::MessageType::ThreadCreated + if msg.kind == serenity::MessageType::ThreadCreated && framework.options.prefix_options.ignore_thread_creation { return Ok(None); @@ -313,10 +313,7 @@ pub async fn run_invocation( // Typing is broadcasted as long as this object is alive let _typing_broadcaster = if ctx.command.broadcast_typing { - ctx.msg - .channel_id - .start_typing(&ctx.serenity_context.http) - .ok() + Some(ctx.msg.channel_id.start_typing(&ctx.serenity_context.http)) } else { None }; diff --git a/src/dispatch/slash.rs b/src/dispatch/slash.rs index 2cf481c0803b..64a6a752f75e 100644 --- a/src/dispatch/slash.rs +++ b/src/dispatch/slash.rs @@ -5,10 +5,10 @@ use crate::serenity_prelude as serenity; /// Check if the interaction with the given name and arguments matches any framework command fn find_matching_command<'a, 'b, U, E>( interaction_name: &str, - interaction_options: &'b [serenity::CommandDataOption], + interaction_options: &'b [serenity::ResolvedOption<'b>], commands: &'a [crate::Command], parent_commands: &mut Vec<&'a crate::Command>, -) -> Option<(&'a crate::Command, &'b [serenity::CommandDataOption])> { +) -> Option<(&'a crate::Command, &'b [serenity::ResolvedOption<'b>])> { commands.iter().find_map(|cmd| { if interaction_name != cmd.name && Some(interaction_name) != cmd.context_menu_name.as_deref() @@ -16,17 +16,17 @@ fn find_matching_command<'a, 'b, U, E>( return None; } - if let Some(sub_interaction) = interaction_options.iter().find(|option| { - option.kind == serenity::CommandOptionType::SubCommand - || option.kind == serenity::CommandOptionType::SubCommandGroup - }) { + if let Some((sub_name, sub_interaction)) = + interaction_options + .iter() + .find_map(|option| match &option.value { + serenity::ResolvedValue::SubCommand(o) + | serenity::ResolvedValue::SubCommandGroup(o) => Some((&option.name, o)), + _ => None, + }) + { parent_commands.push(cmd); - find_matching_command( - &sub_interaction.name, - &sub_interaction.options, - &cmd.subcommands, - parent_commands, - ) + find_matching_command(sub_name, sub_interaction, &cmd.subcommands, parent_commands) } else { Some((cmd, interaction_options)) } @@ -37,17 +37,20 @@ fn find_matching_command<'a, 'b, U, E>( /// /// After this, the [`crate::ApplicationContext`] should be passed into [`run_command`] or /// [`run_autocomplete`]. +#[allow(clippy::too_many_arguments)] // We need to pass them all in to create Context. fn extract_command<'a, U, E>( framework: crate::FrameworkContext<'a, U, E>, ctx: &'a serenity::Context, - interaction: crate::ApplicationCommandOrAutocompleteInteraction<'a>, + interaction: &'a serenity::CommandInteraction, + interaction_type: crate::CommandInteractionType, has_sent_initial_response: &'a std::sync::atomic::AtomicBool, invocation_data: &'a tokio::sync::Mutex>, + options: &'a [serenity::ResolvedOption<'a>], parent_commands: &'a mut Vec<&'a crate::Command>, ) -> Result, crate::FrameworkError<'a, U, E>> { let search_result = find_matching_command( - &interaction.data().name, - &interaction.data().options, + &interaction.data.name, + options, &framework.options.commands, parent_commands, ); @@ -63,6 +66,7 @@ fn extract_command<'a, U, E>( serenity_context: ctx, framework, interaction, + interaction_type, args: leaf_interaction_options, command, parent_commands, @@ -72,22 +76,26 @@ fn extract_command<'a, U, E>( }) } -/// Given an interaction, finds the matching framework command and checks if the user is allowed -/// access +/// Given an interaction, finds the matching framework command and checks if the user is allowed access +#[allow(clippy::too_many_arguments)] // We need to pass them all in to create Context. pub async fn extract_command_and_run_checks<'a, U, E>( framework: crate::FrameworkContext<'a, U, E>, ctx: &'a serenity::Context, - interaction: crate::ApplicationCommandOrAutocompleteInteraction<'a>, + interaction: &'a serenity::CommandInteraction, + interaction_type: crate::CommandInteractionType, has_sent_initial_response: &'a std::sync::atomic::AtomicBool, invocation_data: &'a tokio::sync::Mutex>, + options: &'a [serenity::ResolvedOption<'a>], parent_commands: &'a mut Vec<&'a crate::Command>, ) -> Result, crate::FrameworkError<'a, U, E>> { let ctx = extract_command( framework, ctx, interaction, + interaction_type, has_sent_initial_response, invocation_data, + options, parent_commands, )?; super::common::check_permissions_and_cooldown(ctx.into()).await?; @@ -110,7 +118,7 @@ async fn run_command( description: "received interaction type but command contained no \ matching action or interaction contained no matching context menu object", }; - let action_result = match ctx.interaction.data().kind { + let action_result = match ctx.interaction.data.kind { serenity::CommandType::ChatInput => { let action = ctx .command @@ -121,24 +129,24 @@ async fn run_command( serenity::CommandType::User => { match ( ctx.command.context_menu_action, - &ctx.interaction.data().target(), + &ctx.interaction.data.target(), ) { ( Some(crate::ContextMenuCommandAction::User(action)), Some(serenity::ResolvedTarget::User(user, _)), - ) => action(ctx, user.clone()).await, + ) => action(ctx, (*user).clone()).await, _ => return Err(command_structure_mismatch_error), } } serenity::CommandType::Message => { match ( ctx.command.context_menu_action, - &ctx.interaction.data().target(), + &ctx.interaction.data.target(), ) { ( Some(crate::ContextMenuCommandAction::Message(action)), Some(serenity::ResolvedTarget::Message(message)), - ) => action(ctx, *message.clone()).await, + ) => action(ctx, (*message).clone()).await, _ => return Err(command_structure_mismatch_error), } } @@ -158,20 +166,23 @@ async fn run_command( pub async fn dispatch_interaction<'a, U, E>( framework: crate::FrameworkContext<'a, U, E>, ctx: &'a serenity::Context, - interaction: &'a serenity::ApplicationCommandInteraction, + interaction: &'a serenity::CommandInteraction, // Need to pass this in from outside because of lifetime issues has_sent_initial_response: &'a std::sync::atomic::AtomicBool, // Need to pass this in from outside because of lifetime issues invocation_data: &'a tokio::sync::Mutex>, // Need to pass this in from outside because of lifetime issues + options: &'a [serenity::ResolvedOption<'a>], parent_commands: &'a mut Vec<&'a crate::Command>, ) -> Result<(), crate::FrameworkError<'a, U, E>> { let ctx = extract_command( framework, ctx, - crate::ApplicationCommandOrAutocompleteInteraction::ApplicationCommand(interaction), + interaction, + crate::CommandInteractionType::Command, has_sent_initial_response, invocation_data, + options, parent_commands, )?; @@ -193,7 +204,10 @@ async fn run_autocomplete( super::common::check_permissions_and_cooldown(ctx.into()).await?; // Find which parameter is focused by the user - let focused_option = match ctx.args.iter().find(|o| o.focused) { + let (focused_option_name, partial_input) = match ctx.args.iter().find_map(|o| match &o.value { + serenity::ResolvedValue::Autocomplete { value, .. } => Some((&o.name, value)), + _ => None, + }) { Some(x) => x, None => { log::warn!("no option is focused in autocomplete interaction"); @@ -205,57 +219,40 @@ async fn run_autocomplete( let parameters = &ctx.command.parameters; let focused_parameter = parameters .iter() - .find(|p| p.name == focused_option.name) + .find(|p| &p.name == focused_option_name) .ok_or(crate::FrameworkError::CommandStructureMismatch { ctx, description: "focused autocomplete parameter name not recognized", })?; // Only continue if this parameter supports autocomplete and Discord has given us a partial value - let (autocomplete_callback, partial_input) = match ( - focused_parameter.autocomplete_callback, - &focused_option.value, - ) { - (Some(a), Some(b)) => (a, b), + let autocomplete_callback = match focused_parameter.autocomplete_callback { + Some(a) => a, _ => return Ok(()), }; #[allow(unused_imports)] - use ::serenity::json::prelude::*; // as_str() access via trait for simd-json + use ::serenity::json::*; // as_str() access via trait for simd-json // Generate an autocomplete response - let partial_input = - partial_input - .as_str() - .ok_or(crate::FrameworkError::CommandStructureMismatch { - ctx, - description: "unexpected non-string autocomplete input", - })?; let autocomplete_response = match autocomplete_callback(ctx, partial_input).await { Ok(x) => x, Err(e) => { - log::warn!("couldn't generate autocomplete response: {}", e); - return Ok(()); - } - }; - - let interaction = match ctx.interaction { - crate::ApplicationCommandOrAutocompleteInteraction::Autocomplete(x) => x, - _ => { - log::warn!("a non-autocomplete interaction was given to run_autocomplete()"); + log::warn!("couldn't generate autocomplete response: {e}"); return Ok(()); } }; // Send the generates autocomplete response - if let Err(e) = interaction - .create_autocomplete_response(&ctx.serenity_context.http, |b| { - *b = autocomplete_response; - b - }) + if let Err(e) = ctx + .interaction + .create_response( + &ctx.serenity_context, + serenity::CreateInteractionResponse::Autocomplete(autocomplete_response), + ) .await { - log::warn!("couldn't send autocomplete response: {}", e); + log::warn!("couldn't send autocomplete response: {e}"); } Ok(()) @@ -266,20 +263,21 @@ async fn run_autocomplete( pub async fn dispatch_autocomplete<'a, U, E>( framework: crate::FrameworkContext<'a, U, E>, ctx: &'a serenity::Context, - interaction: &'a serenity::AutocompleteInteraction, - // Need to pass this in from outside because of lifetime issues + interaction: &'a serenity::CommandInteraction, + // Need to pass the following in from outside because of lifetime issues has_sent_initial_response: &'a std::sync::atomic::AtomicBool, - // Need to pass this in from outside because of lifetime issues invocation_data: &'a tokio::sync::Mutex>, - // Need to pass this in from outside because of lifetime issues + options: &'a [serenity::ResolvedOption<'a>], parent_commands: &'a mut Vec<&'a crate::Command>, ) -> Result<(), crate::FrameworkError<'a, U, E>> { let ctx = extract_command( framework, ctx, - crate::ApplicationCommandOrAutocompleteInteraction::Autocomplete(interaction), + interaction, + crate::CommandInteractionType::Autocomplete, has_sent_initial_response, invocation_data, + options, parent_commands, )?; diff --git a/src/event.rs b/src/event.rs deleted file mode 100644 index 04481e10105c..000000000000 --- a/src/event.rs +++ /dev/null @@ -1,196 +0,0 @@ -//! Provides a utility `EventHandler` that generates [`Event`] enum instances for incoming events. - -use crate::{serenity_prelude as serenity, BoxFuture}; - -/// A [`serenity::EventHandler`] implementation that wraps every received event into the [`Event`] -/// enum and propagates it to a callback. -/// -/// Packaging every event into a singular type can make it easier to pass around and process. -/// -/// ```rust -/// use serenity::prelude::*; -/// -/// let handler = poise::EventWrapper(|ctx, event| Box::pin(async move { -/// println!("Received an event: {:?}", event); -/// })); -/// -/// # let token = ""; -/// # let intents = GatewayIntents::empty(); -/// Client::builder(token, intents) -/// .event_handler(handler) -/// // ... -/// # ; -/// ``` -pub struct EventWrapper(pub F) -where - // gotta have this generic bound in the struct as well, or type inference will break down the line - F: Send + Sync + for<'a> Fn(serenity::Context, Event<'a>) -> BoxFuture<'a, ()>; - -/// Small macro to concisely generate the `EventWrapper` code while handling every possible event -macro_rules! event { - ($lt1:lifetime $( - $( #[$attr:meta] )? - $fn_name:ident $(<$lt2:lifetime>)? => $variant_name:ident { $( $arg_name:ident: $arg_type:ty ),* }, - )*) => { - #[serenity::async_trait] - impl serenity::EventHandler for EventWrapper - where - F: Send + Sync + for<'a> Fn(serenity::Context, Event<'a>) -> BoxFuture<'a, ()> - { - $( - $( #[$attr] )? - async fn $fn_name<'s $(, $lt2)? >(&'s self, ctx: serenity::Context, $( $arg_name: $arg_type, )* ) { - (self.0)(ctx, Event::$variant_name { $( $arg_name, )* }).await - } - )* - } - - /// This enum stores every possible event that a [`serenity::EventHandler`] can receive. - /// - /// Passed to the stored callback by [`EventWrapper`]. - #[allow(clippy::large_enum_variant)] - #[allow(missing_docs)] - #[derive(Debug, Clone)] - pub enum Event<$lt1> { - $( - $( #[$attr] )? - $variant_name { $( $arg_name: $arg_type ),* }, - )* - // #[non_exhaustive] forbids struct update syntax for ?? reason - #[doc(hidden)] - __NonExhaustive(std::convert::Infallible), - } - - impl Event<'_> { - /// Return the name of the event type - pub fn name(&self) -> &'static str { - match self { - $( - $( #[$attr] )? - Self::$variant_name { .. } => stringify!($variant_name), - )* - Self::__NonExhaustive(unreachable) => match *unreachable {}, - } - } - - /// Runs this event in the given [`serenity::EventHandler`] - pub async fn dispatch(self, ctx: serenity::Context, handler: &dyn serenity::EventHandler) { - match self { - $( - $( #[$attr] )? - Self::$variant_name { $( $arg_name ),* } => { - handler.$fn_name( ctx, $( $arg_name ),* ).await; - } - )* - Self::__NonExhaustive(unreachable) => match unreachable {}, - } - } - } - }; -} - -// generated from serenity/client/event_handler.rs -// with help from vscode multiline editing and some manual cleanup -event! { - 'a - application_command_permissions_update => ApplicationCommandPermissionsUpdate { permission: serenity::CommandPermission }, - auto_moderation_action_execution => AutoModerationActionExecution { execution: serenity::ActionExecution }, - auto_moderation_rule_create => AutoModerationRuleCreate{ rule: serenity::Rule }, - auto_moderation_rule_update => AutoModerationRuleUpdate{ rule: serenity::Rule }, - auto_moderation_rule_delete => AutoModerationRuleDelete{ rule: serenity::Rule }, - #[cfg(feature = "cache")] - cache_ready => CacheReady { guilds: Vec }, - channel_create<'a> => ChannelCreate { channel: &'a serenity::GuildChannel }, - category_create<'a> => CategoryCreate { category: &'a serenity::ChannelCategory }, - category_delete<'a> => CategoryDelete { category: &'a serenity::ChannelCategory }, - channel_delete<'a> => ChannelDelete { channel: &'a serenity::GuildChannel }, - channel_pins_update => ChannelPinsUpdate { pin: serenity::ChannelPinsUpdateEvent }, - #[cfg(feature = "cache")] - channel_update => ChannelUpdate { old: Option, new: serenity::Channel }, - #[cfg(not(feature = "cache"))] - channel_update => ChannelUpdate { new: serenity::Channel }, - guild_ban_addition => GuildBanAddition { guild_id: serenity::GuildId, banned_user: serenity::User }, - guild_ban_removal => GuildBanRemoval { guild_id: serenity::GuildId, unbanned_user: serenity::User }, - #[cfg(feature = "cache")] - guild_create => GuildCreate { guild: serenity::Guild, is_new: bool }, - #[cfg(not(feature = "cache"))] - guild_create => GuildCreate { guild: serenity::Guild }, - #[cfg(feature = "cache")] - guild_delete => GuildDelete { incomplete: serenity::UnavailableGuild, full: Option }, - #[cfg(not(feature = "cache"))] - guild_delete => GuildDelete { incomplete: serenity::UnavailableGuild }, - guild_emojis_update => GuildEmojisUpdate { guild_id: serenity::GuildId, current_state: std::collections::HashMap }, - guild_integrations_update => GuildIntegrationsUpdate { guild_id: serenity::GuildId }, - guild_member_addition => GuildMemberAddition { new_member: serenity::Member }, - #[cfg(feature = "cache")] - guild_member_removal => GuildMemberRemoval { guild_id: serenity::GuildId, user: serenity::User, member_data_if_available: Option }, - #[cfg(not(feature = "cache"))] - guild_member_removal => GuildMemberRemoval { guild_id: serenity::GuildId, user: serenity::User }, - #[cfg(feature = "cache")] - guild_member_update => GuildMemberUpdate { old_if_available: Option, new: serenity::Member }, - #[cfg(not(feature = "cache"))] - guild_member_update => GuildMemberUpdate { data: serenity::GuildMemberUpdateEvent }, - guild_members_chunk => GuildMembersChunk { chunk: serenity::GuildMembersChunkEvent }, - guild_role_create => GuildRoleCreate { new: serenity::Role }, - #[cfg(feature = "cache")] - guild_role_delete => GuildRoleDelete { guild_id: serenity::GuildId, removed_role_id: serenity::RoleId, removed_role_data_if_available: Option }, - #[cfg(not(feature = "cache"))] - guild_role_delete => GuildRoleDelete { guild_id: serenity::GuildId, removed_role_id: serenity::RoleId }, - #[cfg(feature = "cache")] - guild_role_update => GuildRoleUpdate { old_data_if_available: Option, new: serenity::Role }, - #[cfg(not(feature = "cache"))] - guild_role_update => GuildRoleUpdate { new: serenity::Role }, - guild_scheduled_event_create => GuildScheduledEventCreate { event: serenity::ScheduledEvent }, - guild_scheduled_event_update => GuildScheduledEventUpdate { event: serenity::ScheduledEvent }, - guild_scheduled_event_delete => GuildScheduledEventDelete { event: serenity::ScheduledEvent }, - guild_scheduled_event_user_add => GuildScheduledEventUserAdd { subscribed: serenity::GuildScheduledEventUserAddEvent }, - guild_scheduled_event_user_remove => GuildScheduledEventUserRemove { unsubscribed: serenity::GuildScheduledEventUserRemoveEvent }, - guild_stickers_update => GuildStickersUpdate { guild_id: serenity::GuildId, current_state: std::collections::HashMap}, - guild_unavailable => GuildUnavailable { guild_id: serenity::GuildId }, - #[cfg(feature = "cache")] - guild_update => GuildUpdate { old_data_if_available: Option, new_but_incomplete: serenity::PartialGuild }, - #[cfg(not(feature = "cache"))] - guild_update => GuildUpdate { new_but_incomplete: serenity::PartialGuild }, - integration_create => IntegrationCreate { integration: serenity::Integration }, - integration_update => IntegrationUpdate { integration: serenity::Integration }, - integration_delete => IntegrationDelete { integration_id: serenity::IntegrationId, guild_id: serenity::GuildId, application_id: Option }, - interaction_create => InteractionCreate { interaction: serenity::Interaction }, - invite_create => InviteCreate { data: serenity::InviteCreateEvent }, - invite_delete => InviteDelete { data: serenity::InviteDeleteEvent }, - message => Message { new_message: serenity::Message }, - message_delete => MessageDelete { channel_id: serenity::ChannelId, deleted_message_id: serenity::MessageId, guild_id: Option }, - message_delete_bulk => MessageDeleteBulk { channel_id: serenity::ChannelId, multiple_deleted_messages_ids: Vec, guild_id: Option }, - #[cfg(feature = "cache")] - message_update => MessageUpdate { old_if_available: Option, new: Option, event: serenity::MessageUpdateEvent }, - #[cfg(not(feature = "cache"))] - message_update => MessageUpdate { event: serenity::MessageUpdateEvent }, - reaction_add => ReactionAdd { add_reaction: serenity::Reaction }, - reaction_remove => ReactionRemove { removed_reaction: serenity::Reaction }, - reaction_remove_all => ReactionRemoveAll { channel_id: serenity::ChannelId, removed_from_message_id: serenity::MessageId }, - presence_replace => PresenceReplace { new_presences: Vec }, - presence_update => PresenceUpdate { new_data: serenity::Presence }, - ready => Ready { data_about_bot: serenity::Ready }, - resume => Resume { event: serenity::ResumedEvent }, - shard_stage_update => ShardStageUpdate { update: serenity::ShardStageUpdateEvent }, - stage_instance_create => StageInstanceCreate { stage_instance: serenity::StageInstance }, - stage_instance_delete => StageInstanceDelete { stage_instance: serenity::StageInstance }, - stage_instance_update => StageInstanceUpdate { stage_instance: serenity::StageInstance }, - thread_create => ThreadCreate { thread: serenity::GuildChannel }, - thread_delete => ThreadDelete { thread: serenity::PartialGuildChannel }, - thread_list_sync => ThreadListSync { thread_list_sync: serenity::ThreadListSyncEvent }, - thread_member_update => ThreadMemberUpdate { thread_member: serenity::ThreadMember }, - thread_members_update => ThreadMembersUpdate { thread_members_update: serenity::ThreadMembersUpdateEvent }, - thread_update => ThreadUpdate { thread: serenity::GuildChannel }, - typing_start => TypingStart { event: serenity::TypingStartEvent }, - unknown => Unknown { name: String, raw: serenity::json::Value }, - #[cfg(feature = "cache")] - user_update => UserUpdate { old_data: serenity::CurrentUser, new: serenity::CurrentUser }, - #[cfg(not(feature = "cache"))] - user_update => UserUpdate { new: serenity::CurrentUser }, - voice_server_update => VoiceServerUpdate { update: serenity::VoiceServerUpdateEvent }, - #[cfg(feature = "cache")] - voice_state_update => VoiceStateUpdate { old: Option, new: serenity::VoiceState }, - #[cfg(not(feature = "cache"))] - voice_state_update => VoiceStateUpdate { new: serenity::VoiceState }, - webhook_update => WebhookUpdate { guild_id: serenity::GuildId, belongs_to_channel_id: serenity::ChannelId }, -} diff --git a/src/framework/builder.rs b/src/framework/builder.rs index f339fb106fc8..1dc388bed991 100644 --- a/src/framework/builder.rs +++ b/src/framework/builder.rs @@ -3,16 +3,11 @@ use crate::serenity_prelude as serenity; use crate::BoxFuture; -/// A builder to configure and run a framework. +/// A builder to configure a framework. /// /// If one of the following required values is missing, the builder will panic on start: -/// - [`Self::token`] /// - [`Self::setup`] /// - [`Self::options`] -/// - [`Self::intents`] -/// -/// Before starting, the builder will make an HTTP request to retrieve the bot's application ID and -/// owner, if [`Self::initialize_owners`] is set (true by default). pub struct FrameworkBuilder { /// Callback for startup code and user data creation setup: Option< @@ -28,13 +23,6 @@ pub struct FrameworkBuilder { >, /// Framework options options: Option>, - /// Client settings that will be applied to the ClientBuilder before initializing the framework - client_settings: - Option serenity::ClientBuilder + Send + Sync>>, - /// Discord bot token - token: Option, - /// Discord gateway intents - intents: Option, /// List of framework commands commands: Vec>, /// See [`Self::initialize_owners()`] @@ -46,9 +34,6 @@ impl Default for FrameworkBuilder { Self { setup: Default::default(), options: Default::default(), - client_settings: Default::default(), - token: Default::default(), - intents: Default::default(), commands: Default::default(), initialize_owners: true, } @@ -56,13 +41,6 @@ impl Default for FrameworkBuilder { } impl FrameworkBuilder { - /// Set a prefix for commands - #[deprecated = "Please set the prefix via FrameworkOptions::prefix_options::prefix"] - #[must_use] - pub fn prefix(self, _prefix: impl Into) -> Self { - panic!("Please set the prefix via FrameworkOptions::prefix_options::prefix"); - } - /// Sets the setup callback which also returns the user data struct. #[must_use] pub fn setup(mut self, setup: F) -> Self @@ -80,23 +58,6 @@ impl FrameworkBuilder { self } - /// Sets the setup callback which also returns the user data struct. - #[must_use] - #[deprecated = "renamed to .setup()"] - pub fn user_data_setup(self, setup: F) -> Self - where - F: Send - + Sync - + 'static - + for<'a> FnOnce( - &'a serenity::Context, - &'a serenity::Ready, - &'a crate::Framework, - ) -> BoxFuture<'a, Result>, - { - self.setup(setup) - } - /// Configure framework options #[must_use] pub fn options(mut self, options: crate::FrameworkOptions) -> Self { @@ -104,74 +65,6 @@ impl FrameworkBuilder { self } - /// Configure serenity client settings, like gateway intents, by supplying a custom - /// client builder - /// - /// Note: the builder's token will be overridden by the - /// [`FrameworkBuilder`]; use [`FrameworkBuilder::token`] to supply a token. - #[must_use] - pub fn client_settings( - mut self, - f: impl FnOnce(serenity::ClientBuilder) -> serenity::ClientBuilder + Send + Sync + 'static, - ) -> Self { - self.client_settings = Some(Box::new(f)); - self - } - - /// The bot token - #[must_use] - pub fn token(mut self, token: impl Into) -> Self { - self.token = Some(token.into()); - self - } - - /// The gateway intents - #[must_use] - pub fn intents(mut self, intents: serenity::GatewayIntents) -> Self { - self.intents = Some(intents); - self - } - - /// Add a new command to the framework - #[deprecated = "supply commands in FrameworkOptions directly with `commands: vec![...]`"] - #[must_use] - pub fn command( - mut self, - mut command: crate::Command, - meta_builder: impl FnOnce(&mut crate::Command) -> &mut crate::Command + 'static, - ) -> Self { - meta_builder(&mut command); - self.commands.push(command); - self - } - - /// Add multiple new commands to the framework. Shorthand for calling [`Self::command`] multiple - /// times with the builder left to defaults, i.e. no command category or subcommands - /// - /// ```rust - /// # type Error = Box; - /// # #[poise::command(prefix_command)] - /// # async fn command1(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> { Ok(()) } - /// # #[poise::command(prefix_command)] - /// # async fn command2(ctx: poise::Context<'_, (), Error>) -> Result<(), Error> { Ok(()) } - /// - /// # #[allow(deprecated)] // just for this example - /// poise::Framework::builder() - /// // framework setup... - /// .commands([command1, command2]) - /// // framework startup... - /// # ; - /// ``` - #[deprecated = "supply commands in FrameworkOptions directly with `commands: vec![...]`"] - #[must_use] - pub fn commands( - mut self, - commands: impl IntoIterator crate::Command> + 'static, - ) -> Self { - self.commands.extend(commands.into_iter().map(|c| c())); - self - } - /// Whether to add this bot application's owner and team members to /// [`crate::FrameworkOptions::owners`] automatically /// @@ -184,24 +77,11 @@ impl FrameworkBuilder { /// Build the framework with the specified configuration. /// /// For more information, see [`FrameworkBuilder`] - pub async fn build(self) -> Result>, serenity::Error> + pub fn build(self) -> crate::Framework where U: Send + Sync + 'static, E: Send + 'static, { - // Aggregate required values or panic if not provided - // WHEN CHANGING THESE, UPDATE FrameworkBuilder docs! - let token = self.token.expect("No token was provided to the framework"); - let intents = self.intents.expect( - " - -No gateway intents were provided to the framework via `FrameworkBuilder::intents()`. If you're \ -unsure, use -`serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT` -and enable MESSAGE_CONTENT in your Discord bot dashboard - -", - ); let setup = self .setup .expect("No user data setup function was provided to the framework"); @@ -209,40 +89,9 @@ and enable MESSAGE_CONTENT in your Discord bot dashboard // Build framework options by concatenating user-set options with commands and owners options.commands.extend(self.commands); - if self.initialize_owners { - if let Err(e) = super::insert_owners_from_http(&token, &mut options.owners).await { - log::warn!("Failed to insert owners from HTTP: {}", e); - } - } - - // Create serenity client - let mut client_builder = serenity::ClientBuilder::new(token, intents); - if let Some(client_settings) = self.client_settings { - client_builder = client_settings(client_builder); - } + options.initialize_owners = self.initialize_owners; // Create framework with specified settings - crate::Framework::new(client_builder, setup, options).await - } - - /// Start the framework with the specified configuration. - /// - /// [`FrameworkBuilder::run`] is just a shorthand that calls [`FrameworkBuilder::build`] and - /// starts the returned framework - pub async fn run(self) -> Result<(), serenity::Error> - where - U: Send + Sync + 'static, - E: Send + 'static, - { - self.build().await?.start().await - } - - /// Autosharded version of [`Self::run`] - pub async fn run_autosharded(self) -> Result<(), serenity::Error> - where - U: Send + Sync + 'static, - E: Send + 'static, - { - self.build().await?.start_autosharded().await + crate::Framework::new(options, setup) } } diff --git a/src/framework/mod.rs b/src/framework/mod.rs index 523a161215d0..d6f821552931 100644 --- a/src/framework/mod.rs +++ b/src/framework/mod.rs @@ -1,5 +1,7 @@ //! The central Framework struct that ties everything together. +use std::sync::Arc; + pub use builder::*; use crate::{serenity_prelude as serenity, BoxFuture}; @@ -18,16 +20,14 @@ mod builder; /// You can build a bot without [`Framework`]: see the `manual_dispatch` example in the repository pub struct Framework { /// Stores user data. Is initialized on first Ready event - user_data: once_cell::sync::OnceCell, + user_data: std::sync::OnceLock, /// Stores bot ID. Is initialized on first Ready event - bot_id: once_cell::sync::OnceCell, + bot_id: std::sync::OnceLock, /// Stores the framework options options: crate::FrameworkOptions, - /// Will be initialized to Some on construction, and then taken out on startup - client: parking_lot::Mutex>, /// Initialized to Some during construction; so shouldn't be None at any observable point - shard_manager: std::sync::Arc>, + shard_manager: Option>, /// Filled with Some on construction. Taken out and executed on first Ready gateway event setup: std::sync::Mutex< Option< @@ -42,6 +42,9 @@ pub struct Framework { >, >, >, + + /// Handle to the background task in order to `abort()` it on `Drop` + edit_tracker_purge_task: Option>, } impl Framework { @@ -62,17 +65,11 @@ impl Framework { /// Setup a new [`Framework`]. For more ergonomic setup, please see [`FrameworkBuilder`] /// - /// This function is async and returns Result because it already initializes the Discord client. - /// /// The user data callback is invoked as soon as the bot is logged in. That way, bot data like /// user ID or connected guilds can be made available to the user data setup function. The user /// data setup is not allowed to return Result because there would be no reasonable /// course of action on error. - pub async fn new( - client_builder: serenity::ClientBuilder, - setup: F, - mut options: crate::FrameworkOptions, - ) -> Result, serenity::Error> + pub fn new(options: crate::FrameworkOptions, setup: F) -> Self where F: Send + Sync @@ -85,108 +82,14 @@ impl Framework { U: Send + Sync + 'static, E: Send + 'static, { - use std::sync::{Arc, Mutex}; - - set_qualified_names(&mut options.commands); - message_content_intent_sanity_check(&options.prefix_options, client_builder.get_intents()); - - let framework_cell = Arc::new(once_cell::sync::OnceCell::>::new()); - let framework_cell_2 = framework_cell.clone(); - let existing_event_handler = client_builder.get_event_handler(); - let event_handler = crate::EventWrapper(move |ctx, event| { - // unwrap_used: we will only receive events once the client has been started, by which - // point framework_cell has been initialized - #[allow(clippy::unwrap_used)] - let framework = framework_cell_2.get().unwrap().clone(); - let existing_event_handler = existing_event_handler.clone(); - - Box::pin(async move { - raw_dispatch_event(&*framework, &ctx, &event).await; - if let Some(handler) = existing_event_handler { - event.dispatch(ctx, &*handler).await; - } - }) as _ - }); - - let client: serenity::Client = client_builder.event_handler(event_handler).await?; - - let framework = Arc::new(Self { - user_data: once_cell::sync::OnceCell::new(), - bot_id: once_cell::sync::OnceCell::new(), - setup: Mutex::new(Some(Box::new(setup))), + Self { + user_data: std::sync::OnceLock::new(), + bot_id: std::sync::OnceLock::new(), + setup: std::sync::Mutex::new(Some(Box::new(setup))), + edit_tracker_purge_task: None, + shard_manager: None, options, - shard_manager: client.shard_manager.clone(), - client: parking_lot::Mutex::new(Some(client)), - }); - let _: Result<_, _> = framework_cell.set(framework.clone()); - Ok(framework) - } - - /// Small utility function for starting the framework that is agnostic over client sharding - /// - /// You can use these shortcut methods to start the framework with a single shard - /// or with automatic sharding: [`Framework::start`], [`Framework::start_autosharded`] - /// - /// # Examples - /// - /// Start shards in a range - /// ```rust,no_run - /// # async fn _test(framework: std::sync::Arc>) -> Result<(), serenity::Error> { - /// let shard_range = [3, 7]; - /// let total_shards = 8; - /// - /// framework - /// .start_with(|mut client| async move { - /// client.start_shard_range(shard_range, total_shards).await - /// }) - /// .await?; - /// # Ok(()) }; - /// ``` - pub async fn start_with>>( - self: std::sync::Arc, - start: impl FnOnce(serenity::Client) -> F, - ) -> Result<(), serenity::Error> - where - U: Send + Sync + 'static, - E: Send + 'static, - { - let client = self - .client - .lock() - .take() - .expect("Prepared client is missing"); - - // This will run for as long as the bot is active - let edit_tracker_purge_task = spawn_edit_tracker_purge_task(self); - start(client).await?; - edit_tracker_purge_task.abort(); - - Ok(()) - } - - /// Starts the framework with a shard. Calls [`serenity::Client::start`] internally. - /// - /// See [`Framework::start_with`] for other sharding configurations. - pub async fn start(self: std::sync::Arc) -> Result<(), serenity::Error> - where - U: Send + Sync + 'static, - E: Send + 'static, - { - self.start_with(|mut c| async move { c.start().await }) - .await - } - - /// Starts the framework with automatic sharding. - /// Calls [`serenity::Client::start_autosharded`] internally. - /// - /// See [`Framework::start_with`] for other sharding configurations. - pub async fn start_autosharded(self: std::sync::Arc) -> Result<(), serenity::Error> - where - U: Send + Sync + 'static, - E: Send + 'static, - { - self.start_with(|mut c| async move { c.start_autosharded().await }) - .await + } } /// Return the stored framework options, including commands. @@ -196,15 +99,10 @@ impl Framework { /// Returns the serenity's client shard manager. // Returns a reference so you can plug it into [`FrameworkContext`] - pub fn shard_manager(&self) -> &std::sync::Arc> { - &self.shard_manager - } - - /// Returns the serenity client. Panics if the framework has already started! - pub fn client(&self) -> impl std::ops::DerefMut + '_ { - parking_lot::MutexGuard::map(self.client.lock(), |c| { - c.as_mut().expect("framework has started") - }) + pub fn shard_manager(&self) -> &Arc { + self.shard_manager + .as_ref() + .expect("framework should have started") } /// Retrieves user data, or blocks until it has been initialized (once the Ready event has been @@ -219,16 +117,54 @@ impl Framework { } } +impl Drop for Framework { + fn drop(&mut self) { + if let Some(task) = &mut self.edit_tracker_purge_task { + task.abort() + } + } +} + +#[serenity::async_trait] +impl serenity::Framework for Framework { + async fn init(&mut self, client: &serenity::Client) { + set_qualified_names(&mut self.options.commands); + + message_content_intent_sanity_check( + &self.options.prefix_options, + client.shard_manager.intents(), + ); + + self.shard_manager = Some(client.shard_manager.clone()); + + if self.options.initialize_owners { + if let Err(e) = insert_owners_from_http(&client.http, &mut self.options.owners).await { + log::warn!("Failed to insert owners from HTTP: {}", e); + } + } + + if let Some(edit_tracker) = &self.options.prefix_options.edit_tracker { + self.edit_tracker_purge_task = + Some(spawn_edit_tracker_purge_task(edit_tracker.clone())); + } + } + + async fn dispatch(&self, event: serenity::FullEvent) { + raw_dispatch_event(self, event).await + } +} + /// If the incoming event is Ready, this method executes the user data setup logic /// Otherwise, it forwards the event to [`crate::dispatch_event`] -async fn raw_dispatch_event( - framework: &crate::Framework, - ctx: &serenity::Context, - event: &crate::Event<'_>, -) where +async fn raw_dispatch_event(framework: &Framework, event: serenity::FullEvent) +where U: Send + Sync, { - if let crate::Event::Ready { data_about_bot } = event { + if let serenity::FullEvent::Ready { + ctx, + data_about_bot, + } = &event + { let _: Result<_, _> = framework.bot_id.set(data_about_bot.user.id); let setup = Option::take(&mut *framework.setup.lock().unwrap()); if let Some(setup) = setup { @@ -261,9 +197,9 @@ async fn raw_dispatch_event( bot_id, options: &framework.options, user_data, - shard_manager: &framework.shard_manager, + shard_manager: framework.shard_manager(), }; - crate::dispatch_event(framework, ctx, event).await; + crate::dispatch_event(framework, event).await; } /// Traverses commands recursively and sets [`crate::Command::qualified_name`] to its actual value @@ -297,14 +233,15 @@ fn message_content_intent_sanity_check( /// Runs [`serenity::Http::get_current_application_info`] and inserts owner data into /// [`crate::FrameworkOptions::owners`] pub async fn insert_owners_from_http( - token: &str, + http: &serenity::Http, owners: &mut std::collections::HashSet, ) -> Result<(), serenity::Error> { - let application_info = serenity::Http::new(token) - .get_current_application_info() - .await?; + let application_info = http.get_current_application_info().await?; + + if let Some(owner) = application_info.owner { + owners.insert(owner.id); + } - owners.insert(application_info.owner.id); if let Some(team) = application_info.team { for member in team.members { // This `if` currently always evaluates to true but it becomes important once @@ -325,17 +262,15 @@ pub async fn insert_owners_from_http( /// NOT PUB because it's not useful to outside users because it requires a full blown Framework /// Because e.g. taking a `PrefixFrameworkOptions` reference won't work because tokio tasks need to be /// 'static -fn spawn_edit_tracker_purge_task( - framework: std::sync::Arc>, +fn spawn_edit_tracker_purge_task( + edit_tracker: Arc>, ) -> tokio::task::JoinHandle<()> { tokio::spawn(async move { - if let Some(edit_tracker) = &framework.options.prefix_options.edit_tracker { - loop { - edit_tracker.write().unwrap().purge(); + loop { + edit_tracker.write().unwrap().purge(); - // not sure if the purging interval should be configurable - tokio::time::sleep(std::time::Duration::from_secs(60)).await; - } + // not sure if the purging interval should be configurable + tokio::time::sleep(std::time::Duration::from_secs(60)).await; } }) } diff --git a/src/lib.rs b/src/lib.rs index 2eaa761d48ec..da915e3bc64f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -220,17 +220,19 @@ async fn error_handler(error: poise::FrameworkError<'_, Data, Error>) { ## Create and configure framework ```rust +# use std::sync::Arc; # type Error = Box; # type Context<'a> = poise::Context<'a, (), Error>; # async fn my_error_function(_: poise::FrameworkError<'_, (), Error>) {} # #[poise::command(prefix_command)] async fn command1(ctx: Context<'_>) -> Result<(), Error> { Ok(()) } # #[poise::command(prefix_command)] async fn command2(ctx: Context<'_>) -> Result<(), Error> { Ok(()) } # #[poise::command(prefix_command)] async fn command3(ctx: Context<'_>) -> Result<(), Error> { Ok(()) } +use poise::serenity_prelude as serenity; # async { // Use `Framework::builder()` to create a framework builder and supply basic data to the framework: -poise::Framework::builder() - .token("...") + +let framework = poise::Framework::builder() .setup(|_, _, _| Box::pin(async move { // construct user data here (invoked when bot connects to Discord) Ok(()) @@ -243,7 +245,7 @@ poise::Framework::builder() on_error: |err| Box::pin(my_error_function(err)), prefix_options: poise::PrefixFrameworkOptions { prefix: Some("~".into()), - edit_tracker: Some(poise::EditTracker::for_timespan(std::time::Duration::from_secs(3600))), + edit_tracker: Some(Arc::new(poise::EditTracker::for_timespan(std::time::Duration::from_secs(3600)))), case_insensitive_commands: true, ..Default::default() }, @@ -258,9 +260,12 @@ poise::Framework::builder() } ], ..Default::default() - }) + }).build(); - .run().await?; +let client = serenity::ClientBuilder::new("...", serenity::GatewayIntents::non_privileged()) + .framework(framework).await; + +client.unwrap().start().await.unwrap(); # Ok::<(), Error>(()) }; ``` @@ -387,7 +392,6 @@ pub mod builtins; pub mod choice_parameter; pub mod cooldown; pub mod dispatch; -pub mod event; pub mod framework; pub mod modal; pub mod prefix_argument; @@ -404,7 +408,7 @@ pub mod macros { #[doc(no_inline)] pub use { - choice_parameter::*, cooldown::*, dispatch::*, event::*, framework::*, macros::*, modal::*, + choice_parameter::*, cooldown::*, dispatch::*, framework::*, macros::*, modal::*, prefix_argument::*, reply::*, slash_argument::*, structs::*, track_edits::*, }; @@ -425,56 +429,7 @@ pub use {async_trait::async_trait, futures_util}; /// use poise::serenity_prelude as serenity; /// ``` pub mod serenity_prelude { - #[doc(no_inline)] - pub use serenity::{ - async_trait, - builder::*, - client::{ - bridge::gateway::{event::*, *}, - *, - }, - collector::*, - http::*, - // Explicit imports to resolve ambiguity between model::prelude::* and - // model::application::interaction::* due to deprecated same-named type aliases - model::{ - application::interaction::{ - Interaction, InteractionResponseType, InteractionType, - MessageFlags as InteractionResponseFlags, MessageInteraction, - }, - // There's two MessageFlags in serenity. The interaction response specific one was - // renamed to InteractionResponseFlags above so we can keep this one's name the same - channel::MessageFlags, - }, - model::{ - application::{ - command::*, - component::*, - interaction::{ - application_command::*, autocomplete::*, message_component::*, modal::*, *, - }, - }, - event::{ - EventType, /* overwrite collision with model::guild::automod::EventType */ - *, - }, - guild::automod::*, - prelude::{ - error, /* overwrite collision with http::error (there's no reason why model::error gets the "error" name and http::error doesn't, but I couldn't find a way to rename both modules, or disable both modules, so I _had_ to keep the confusing "error" name for the module) */ - event, /* overwrite collision with client::bridge::gateway::event */ - Action, /* overwrite collision with model::guild::automod::Action */ - *, - }, - }, - prelude::*, - utils::*, - { - client, /* overwrite collision with http::client */ - gateway, /* overwrite collision with client::bridge::gateway */ - prelude, /* overwrite collision with prelude::prelude (?) */ - *, - }, - }; + pub use serenity::all::*; } use serenity_prelude as serenity; // private alias for crate root docs intradoc-links diff --git a/src/modal.rs b/src/modal.rs index 5cf4086b23a4..4351abf1b3a0 100644 --- a/src/modal.rs +++ b/src/modal.rs @@ -1,7 +1,5 @@ //! Modal trait and utility items for implementing it (mainly for the derive macro) -use std::sync::Arc; - use crate::serenity_prelude as serenity; /// Meant for use in derived [`Modal::parse`] implementation @@ -9,7 +7,7 @@ use crate::serenity_prelude as serenity; /// _Takes_ the String out of the data. Logs warnings on unexpected state #[doc(hidden)] pub fn find_modal_text( - data: &mut serenity::ModalSubmitInteractionData, + data: &mut serenity::ModalInteractionData, custom_id: &str, ) -> Option { for row in &mut data.components { @@ -27,7 +25,7 @@ pub fn find_modal_text( if text.custom_id == custom_id { let value = std::mem::take(&mut text.value); - return if value.is_empty() { None } else { Some(value) }; + return value.and_then(|val| (!val.is_empty()).then_some(val)); } } log::warn!( @@ -44,7 +42,7 @@ async fn execute_modal_generic< F: std::future::Future>, >( ctx: &serenity::Context, - create_interaction_response: impl FnOnce(serenity::CreateInteractionResponse<'static>) -> F, + create_interaction_response: impl FnOnce(serenity::CreateInteractionResponse) -> F, modal_custom_id: String, defaults: Option, timeout: Option, @@ -53,7 +51,7 @@ async fn execute_modal_generic< create_interaction_response(M::create(defaults, modal_custom_id.clone())).await?; // Wait for user to submit - let response = serenity::CollectModalInteraction::new(&ctx.shard) + let response = serenity::collector::ModalInteractionCollector::new(&ctx.shard) .filter(move |d| d.data.custom_id == modal_custom_id) .timeout(timeout.unwrap_or(std::time::Duration::from_secs(3600))) .await; @@ -64,9 +62,7 @@ async fn execute_modal_generic< // Send acknowledgement so that the pop-up is closed response - .create_interaction_response(ctx, |b| { - b.kind(serenity::InteractionResponseType::DeferredUpdateMessage) - }) + .create_response(ctx, serenity::CreateInteractionResponse::Acknowledge) .await?; Ok(Some( @@ -94,15 +90,10 @@ pub async fn execute_modal( defaults: Option, timeout: Option, ) -> Result, serenity::Error> { - let interaction = ctx.interaction.unwrap(); + let interaction = ctx.interaction; let response = execute_modal_generic( ctx.serenity_context, - |resp| { - interaction.create_interaction_response(ctx.http(), |b| { - *b = resp; - b - }) - }, + |resp| interaction.create_response(ctx, resp), interaction.id.to_string(), defaults, timeout, @@ -127,18 +118,13 @@ pub async fn execute_modal( /// and adjust to your needs. The code of this function is just a starting point. pub async fn execute_modal_on_component_interaction( ctx: impl AsRef, - interaction: Arc, + interaction: serenity::ModalInteraction, defaults: Option, timeout: Option, ) -> Result, serenity::Error> { execute_modal_generic( ctx.as_ref(), - |resp| { - interaction.create_interaction_response(ctx.as_ref(), |b| { - *b = resp; - b - }) - }, + |resp| interaction.create_response(ctx.as_ref(), resp), interaction.id.to_string(), defaults, timeout, @@ -186,16 +172,13 @@ pub trait Modal: Sized { /// /// Optionally takes an initialized instance as pre-filled values of this modal (see /// [`Self::execute_with_defaults()`] for more info) - fn create( - defaults: Option, - custom_id: String, - ) -> serenity::CreateInteractionResponse<'static>; + fn create(defaults: Option, custom_id: String) -> serenity::CreateInteractionResponse; /// Parses a received modal submit interaction into this type /// /// Returns an error if a field was missing. This should never happen, because Discord will only /// let users submit when all required fields are filled properly - fn parse(data: serenity::ModalSubmitInteractionData) -> Result; + fn parse(data: serenity::ModalInteractionData) -> Result; /// Calls `execute_modal(ctx, None, None)`. See [`execute_modal`] /// diff --git a/src/prefix_argument/macros.rs b/src/prefix_argument/macros.rs index 19cce9ef5f1d..3a86d3884c4f 100644 --- a/src/prefix_argument/macros.rs +++ b/src/prefix_argument/macros.rs @@ -175,15 +175,13 @@ Macro for parsing an argument string into multiple parameter types. An invocation of this macro is generated by the [`crate::command`] macro, so you usually don't need to use this macro directly. -```rust +```rust,no_run # #[tokio::main] async fn main() -> Result<(), Box> { # use poise::serenity_prelude as serenity; # let ctx = serenity::Context { -# data: std::sync::Arc::new(serenity::RwLock::new(serenity::TypeMap::new())), -# shard: ::serenity::client::bridge::gateway::ShardMessenger::new( -# futures::channel::mpsc::unbounded().0, -# ), -# shard_id: Default::default(), +# data: Default::default(), +# shard: todo!(), +# shard_id: todo!(), # http: std::sync::Arc::new(::serenity::http::Http::new("example")), # #[cfg(feature = "cache")] # cache: Default::default(), @@ -250,15 +248,16 @@ mod test { use super::*; #[tokio::test] + #[cfg(not(test))] // Cannot fake serenity Context anymore async fn test_parse_args() { use crate::serenity_prelude as serenity; // Create dummy discord context; it will not be accessed in this test let ctx = serenity::Context { - data: std::sync::Arc::new(serenity::RwLock::new(serenity::TypeMap::new())), - shard: ::serenity::client::bridge::gateway::ShardMessenger::new( - futures::channel::mpsc::unbounded().0, - ), + data: std::sync::Arc::new(serenity::prelude::RwLock::new( + serenity::prelude::TypeMap::new(), + )), + shard: serenity::ShardMessenger::new(futures::channel::mpsc::unbounded().0), shard_id: Default::default(), http: std::sync::Arc::new(::serenity::http::Http::new("example")), #[cfg(feature = "cache")] diff --git a/src/reply/builder.rs b/src/reply/builder.rs index 194844cc7bea..c90b618f41c5 100644 --- a/src/reply/builder.rs +++ b/src/reply/builder.rs @@ -4,17 +4,17 @@ use crate::serenity_prelude as serenity; /// Message builder that abstracts over prefix and application command responses #[derive(Default, Clone)] -pub struct CreateReply<'att> { +pub struct CreateReply { /// Message content. pub content: Option, /// Embeds, if present. pub embeds: Vec, /// Message attachments. - pub attachments: Vec>, + pub attachments: Vec, /// Whether the message is ephemeral (only has an effect in application commands) pub ephemeral: Option, /// Message components, that is, buttons and select menus. - pub components: Option, + pub components: Option>, /// The allowed mentions for the message. pub allowed_mentions: Option, /// Whether this message is an inline reply. @@ -23,7 +23,7 @@ pub struct CreateReply<'att> { pub __non_exhaustive: (), } -impl<'att> CreateReply<'att> { +impl CreateReply { /// Set the content of the message. pub fn content(mut self, content: impl Into) -> Self { self.content = Some(content.into()); @@ -41,7 +41,7 @@ impl<'att> CreateReply<'att> { /// Set components (buttons and select menus) for this message. /// /// Any previously set components will be overwritten. - pub fn components(mut self, components: serenity::CreateComponents) -> Self { + pub fn components(mut self, components: Vec) -> Self { self.components = Some(components); self } @@ -49,7 +49,7 @@ impl<'att> CreateReply<'att> { /// Add an attachment. /// /// This will not have an effect in a slash command's initial response! - pub fn attachment(mut self, attachment: serenity::AttachmentType<'att>) -> Self { + pub fn attachment(mut self, attachment: serenity::CreateAttachment) -> Self { self.attachments.push(attachment); self } @@ -83,9 +83,9 @@ impl<'att> CreateReply<'att> { /// Methods to create a message builder from any type from this [`CreateReply`]. Used by poise /// internally to actually send a response to Discord -impl<'att> CreateReply<'att> { - /// Serialize this response builder to a [`serenity::CreateInteractionResponseData`] - pub fn to_slash_initial_response(self, f: &mut serenity::CreateInteractionResponseData<'att>) { +impl CreateReply { + /// Serialize this response builder to a [`serenity::CreateInteractionResponseMessage`] + pub fn to_slash_initial_response(self) -> serenity::CreateInteractionResponseMessage { let crate::CreateReply { content, embeds, @@ -97,33 +97,26 @@ impl<'att> CreateReply<'att> { __non_exhaustive: (), } = self; + let mut builder = serenity::CreateInteractionResponseMessage::new(); if let Some(content) = content { - f.content(content); + builder = builder.content(content); } - f.set_embeds(embeds); + builder = builder.embeds(embeds); if let Some(allowed_mentions) = allowed_mentions { - f.allowed_mentions(|f| { - *f = allowed_mentions.clone(); - f - }); + builder = builder.allowed_mentions(allowed_mentions); } if let Some(components) = components { - f.components(|f| { - f.0 = components.0; - f - }); + builder = builder.components(components); } if let Some(ephemeral) = ephemeral { - f.ephemeral(ephemeral); + builder = builder.ephemeral(ephemeral); } - f.add_files(attachments); + + builder.add_files(attachments) } /// Serialize this response builder to a [`serenity::CreateInteractionResponseFollowup`] - pub fn to_slash_followup_response( - self, - f: &mut serenity::CreateInteractionResponseFollowup<'att>, - ) { + pub fn to_slash_followup_response(self) -> serenity::CreateInteractionResponseFollowup { let crate::CreateReply { content, embeds, @@ -135,30 +128,26 @@ impl<'att> CreateReply<'att> { __non_exhaustive: (), } = self; + let mut builder = serenity::CreateInteractionResponseFollowup::new(); if let Some(content) = content { - f.content(content); + builder = builder.content(content); } - f.set_embeds(embeds); + builder = builder.embeds(embeds); if let Some(components) = components { - f.components(|c| { - c.0 = components.0; - c - }); + builder = builder.components(components) } if let Some(allowed_mentions) = allowed_mentions { - f.allowed_mentions(|f| { - *f = allowed_mentions.clone(); - f - }); + builder = builder.allowed_mentions(allowed_mentions); } if let Some(ephemeral) = ephemeral { - f.ephemeral(ephemeral); + builder = builder.ephemeral(ephemeral); } - f.add_files(attachments); + + builder.add_files(attachments) } /// Serialize this response builder to a [`serenity::EditInteractionResponse`] - pub fn to_slash_initial_response_edit(self, f: &mut serenity::EditInteractionResponse) { + pub fn to_slash_initial_response_edit(self) -> serenity::EditInteractionResponse { let crate::CreateReply { content, embeds, @@ -170,26 +159,22 @@ impl<'att> CreateReply<'att> { __non_exhaustive: (), } = self; + let mut builder = serenity::EditInteractionResponse::new(); if let Some(content) = content { - f.content(content); + builder = builder.content(content); } - f.set_embeds(embeds); if let Some(components) = components { - f.components(|c| { - c.0 = components.0; - c - }); + builder = builder.components(components); } if let Some(allowed_mentions) = allowed_mentions { - f.allowed_mentions(|f| { - *f = allowed_mentions.clone(); - f - }); + builder = builder.allowed_mentions(allowed_mentions); } + + builder.embeds(embeds) } /// Serialize this response builder to a [`serenity::EditMessage`] - pub fn to_prefix_edit(self, f: &mut serenity::EditMessage<'att>) { + pub fn to_prefix_edit(self) -> serenity::EditMessage { let crate::CreateReply { content, embeds, @@ -201,35 +186,38 @@ impl<'att> CreateReply<'att> { __non_exhaustive: (), } = self; - if let Some(content) = content { - f.content(content); - } - f.add_embeds(embeds); + // Reset the message. We don't want leftovers of the previous message (e.g. user + // sends a message with `.content("abc")` in a track_edits command, and the edited + // message happens to contain embeds, we don't want to keep those embeds) + // (*f = Default::default() won't do) + let mut attachments_builder = serenity::EditAttachments::new(); for attachment in attachments { - f.attachment(attachment); + attachments_builder = attachments_builder.add(attachment); } + let mut builder = serenity::EditMessage::new().attachments(attachments_builder); + if let Some(content) = content { + builder = builder.content(content); + } else { + builder = builder.content(""); + } if let Some(allowed_mentions) = allowed_mentions { - f.allowed_mentions(|b| { - *b = allowed_mentions; - b - }); + builder = builder.allowed_mentions(allowed_mentions); } - if let Some(components) = components { - f.components(|f| { - *f = components; - f - }); + builder = builder.components(components); + } else { + builder = builder.components(Vec::new()); } + + builder.embeds(embeds) } /// Serialize this response builder to a [`serenity::CreateMessage`] pub fn to_prefix( self, - m: &mut serenity::CreateMessage<'att>, - invocation_message: &serenity::Message, - ) { + invocation_message: serenity::MessageReference, + ) -> serenity::CreateMessage { let crate::CreateReply { content, embeds, @@ -241,28 +229,24 @@ impl<'att> CreateReply<'att> { __non_exhaustive: (), } = self; + let mut builder = serenity::CreateMessage::new(); if let Some(content) = content { - m.content(content); + builder = builder.content(content); } - m.set_embeds(embeds); if let Some(allowed_mentions) = allowed_mentions { - m.allowed_mentions(|m| { - *m = allowed_mentions; - m - }); + builder = builder.allowed_mentions(allowed_mentions); } if let Some(components) = components { - m.components(|c| { - c.0 = components.0; - c - }); + builder = builder.components(components); } if reply { - m.reference_message(invocation_message); + builder = builder.reference_message(invocation_message); } for attachment in attachments { - m.add_file(attachment); + builder = builder.add_file(attachment); } + + builder.embeds(embeds) } } diff --git a/src/reply/mod.rs b/src/reply/mod.rs index 83b982c5321d..6ce5e3237afe 100644 --- a/src/reply/mod.rs +++ b/src/reply/mod.rs @@ -21,7 +21,7 @@ enum ReplyHandleInner<'a> { http: &'a serenity::Http, /// Interaction which contains the necessary data to request the interaction response /// message object - interaction: &'a serenity::ApplicationCommandInteraction, + interaction: &'a serenity::CommandInteraction, /// If this is a followup response, the Message object (which Discord only returns for /// followup responses, not initial) followup: Option>, @@ -59,7 +59,7 @@ impl ReplyHandle<'_> { http, interaction, followup: None, - } => interaction.get_interaction_response(http).await, + } => interaction.get_response(http).await, Autocomplete => panic!("reply is a no-op in autocomplete context"), } } @@ -84,9 +84,7 @@ impl ReplyHandle<'_> { http, interaction, followup: None, - } => Ok(Cow::Owned( - interaction.get_interaction_response(http).await?, - )), + } => Ok(Cow::Owned(interaction.get_response(http).await?)), Autocomplete => panic!("reply is a no-op in autocomplete context"), } } @@ -98,24 +96,14 @@ impl ReplyHandle<'_> { pub async fn edit<'att, U, E>( &self, ctx: crate::Context<'_, U, E>, - builder: CreateReply<'att>, + builder: CreateReply, ) -> Result<(), serenity::Error> { let reply = ctx.reply_builder(builder); match &self.0 { ReplyHandleInner::Prefix(msg) => { msg.clone() - .edit(ctx.serenity_context(), |b| { - // Clear builder so that adding embeds or attachments won't add on top of - // the pre-edit items but replace them (which is apparently the more - // intuitive behavior). Notably, setting the builder to default doesn't - // mean the entire message is reset to empty: Discord only updates parts - // of the message that have had a modification specified - *b = Default::default(); - - reply.to_prefix_edit(b); - b - }) + .edit(ctx.serenity_context(), reply.to_prefix_edit()) .await?; } ReplyHandleInner::Application { @@ -124,10 +112,7 @@ impl ReplyHandle<'_> { followup: None, } => { interaction - .edit_original_interaction_response(http, |b| { - reply.to_slash_initial_response_edit(b); - b - }) + .edit_response(http, reply.to_slash_initial_response_edit()) .await?; } ReplyHandleInner::Application { @@ -136,10 +121,7 @@ impl ReplyHandle<'_> { followup: Some(msg), } => { interaction - .edit_followup_message(http, msg.id, |b| { - reply.to_slash_followup_response(b); - b - }) + .edit_followup(http, msg.id, reply.to_slash_followup_response()) .await?; } ReplyHandleInner::Autocomplete => panic!("reply is a no-op in autocomplete context"), @@ -157,14 +139,10 @@ impl ReplyHandle<'_> { followup, } => match followup { Some(followup) => { - interaction - .delete_followup_message(ctx, followup.id) - .await?; + interaction.delete_followup(ctx, followup.id).await?; } None => { - interaction - .delete_original_interaction_response(ctx) - .await?; + interaction.delete_response(ctx).await?; } }, ReplyHandleInner::Autocomplete => panic!("delete is a no-op in autocomplete context"), diff --git a/src/reply/send_reply.rs b/src/reply/send_reply.rs index c13b2956ee89..de193daf4794 100644 --- a/src/reply/send_reply.rs +++ b/src/reply/send_reply.rs @@ -10,11 +10,12 @@ use crate::serenity_prelude as serenity; /// Note: panics when called in an autocomplete context! /// /// ```rust,no_run +/// # use poise::serenity_prelude as serenity; /// # #[tokio::main] async fn main() -> Result<(), Box> { /// # let ctx: poise::Context<'_, (), ()> = todo!(); -/// ctx.send(|f| f +/// ctx.send(poise::CreateReply::default() /// .content("Works for slash and prefix commands") -/// .embed(|f| f +/// .embed(serenity::CreateEmbed::new() /// .title("Much versatile, very wow") /// .description("I need more documentation ok?") /// ) @@ -22,10 +23,10 @@ use crate::serenity_prelude as serenity; /// ).await?; /// # Ok(()) } /// ``` -pub async fn send_reply<'a, U, E>( - ctx: crate::Context<'a, U, E>, - builder: crate::CreateReply<'_>, -) -> Result, serenity::Error> { +pub async fn send_reply( + ctx: crate::Context<'_, U, E>, + builder: crate::CreateReply, +) -> Result, serenity::Error> { Ok(match ctx { crate::Context::Prefix(ctx) => super::ReplyHandle(super::ReplyHandleInner::Prefix( crate::send_prefix_reply(ctx, builder).await?, @@ -47,21 +48,18 @@ pub async fn say_reply( /// Send a response to an interaction (slash command or context menu command invocation). /// /// If a response to this interaction has already been sent, a -/// [followup](serenity::ApplicationCommandInteraction::create_followup_message) is sent. +/// [followup](serenity::CommandInteraction::create_followup_message) is sent. /// /// No-op if autocomplete context -pub async fn send_application_reply<'a, U, E>( - ctx: crate::ApplicationContext<'a, U, E>, - builder: crate::CreateReply<'_>, -) -> Result, serenity::Error> { +pub async fn send_application_reply( + ctx: crate::ApplicationContext<'_, U, E>, + builder: crate::CreateReply, +) -> Result, serenity::Error> { let builder = ctx.reply_builder(builder); - let interaction = match ctx.interaction { - crate::ApplicationCommandOrAutocompleteInteraction::ApplicationCommand(x) => x, - crate::ApplicationCommandOrAutocompleteInteraction::Autocomplete(_) => { - return Ok(super::ReplyHandle(super::ReplyHandleInner::Autocomplete)) - } - }; + if ctx.interaction_type == crate::CommandInteractionType::Autocomplete { + return Ok(super::ReplyHandle(super::ReplyHandleInner::Autocomplete)); + } let has_sent_initial_response = ctx .has_sent_initial_response @@ -69,22 +67,16 @@ pub async fn send_application_reply<'a, U, E>( let followup = if has_sent_initial_response { Some(Box::new( - interaction - .create_followup_message(ctx.serenity_context, |f| { - builder.to_slash_followup_response(f); - f - }) + ctx.interaction + .create_followup(ctx.serenity_context, builder.to_slash_followup_response()) .await?, )) } else { - interaction - .create_interaction_response(ctx.serenity_context, |r| { - r.kind(serenity::InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|f| { - builder.to_slash_initial_response(f); - f - }) - }) + ctx.interaction + .create_response( + ctx.serenity_context, + serenity::CreateInteractionResponse::Message(builder.to_slash_initial_response()), + ) .await?; ctx.has_sent_initial_response .store(true, std::sync::atomic::Ordering::SeqCst); @@ -94,15 +86,15 @@ pub async fn send_application_reply<'a, U, E>( Ok(super::ReplyHandle(super::ReplyHandleInner::Application { http: &ctx.serenity_context.http, - interaction, + interaction: ctx.interaction, followup, })) } /// Prefix-specific reply function. For more details, see [`crate::send_reply`]. -pub async fn send_prefix_reply<'a, U, E>( - ctx: crate::PrefixContext<'a, U, E>, - builder: crate::CreateReply<'_>, +pub async fn send_prefix_reply( + ctx: crate::PrefixContext<'_, U, E>, + builder: crate::CreateReply, ) -> Result, serenity::Error> { let builder = ctx.reply_builder(builder); @@ -126,19 +118,7 @@ pub async fn send_prefix_reply<'a, U, E>( Ok(Box::new(if let Some(mut response) = existing_response { response - .edit(ctx.serenity_context, |f| { - // Reset the message. We don't want leftovers of the previous message (e.g. user - // sends a message with `.content("abc")` in a track_edits command, and the edited - // message happens to contain embeds, we don't want to keep those embeds) - // (*f = Default::default() won't do) - f.content(""); - f.set_embeds(Vec::new()); - f.components(|b| b); - f.0.insert("attachments", serenity::json::json! { [] }); - - builder.to_prefix_edit(f); - f - }) + .edit(ctx.serenity_context, builder.to_prefix_edit()) .await?; // If the entry still exists after the await, update it to the new contents @@ -152,10 +132,7 @@ pub async fn send_prefix_reply<'a, U, E>( let new_response = ctx .msg .channel_id - .send_message(ctx.serenity_context, |m| { - builder.to_prefix(m, ctx.msg); - m - }) + .send_message(ctx.serenity_context, builder.to_prefix(ctx.msg.into())) .await?; // We don't check ctx.command.reuse_response because we need to store bot responses for // track_deletion too diff --git a/src/slash_argument/autocompletable.rs b/src/slash_argument/autocompletable.rs deleted file mode 100644 index 1e6a436576ef..000000000000 --- a/src/slash_argument/autocompletable.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! Hosts just the `AutocompleteChoice` type. This type will probably move somewhere else - -/// A single autocomplete choice, displayed in Discord UI -/// -/// This type can be returned by functions set via the `#[autocomplete = ]` attribute on slash -/// command parameters. -/// -/// For more information, see the autocomplete.rs file in the `feature_showcase` example -pub struct AutocompleteChoice { - /// Label of the choice, displayed in the Discord UI - pub label: String, - /// Value of the choice, sent to the bot - pub value: T, - #[doc(hidden)] - pub __non_exhaustive: (), -} - -impl AutocompleteChoice { - /// Creates a new autocomplete choice with the given text - pub fn new(value: T) -> AutocompleteChoice - where - T: ToString, - { - Self { - label: value.to_string(), - value, - __non_exhaustive: (), - } - } - - /// Like [`Self::new()`], but you can customize the JSON value sent to Discord as the unique - /// identifier of this autocomplete choice. - pub fn new_with_value(label: impl Into, value: T) -> Self { - Self { - label: label.into(), - value, - __non_exhaustive: (), - } - } -} - -impl From for AutocompleteChoice { - fn from(value: T) -> Self { - Self { - label: value.to_string(), - value, - __non_exhaustive: (), - } - } -} diff --git a/src/slash_argument/mod.rs b/src/slash_argument/mod.rs index 58f4fd363b20..8911883cd2c1 100644 --- a/src/slash_argument/mod.rs +++ b/src/slash_argument/mod.rs @@ -9,8 +9,5 @@ pub use slash_trait::*; mod context_menu; pub use context_menu::*; -mod autocompletable; -pub use autocompletable::*; - mod into_stream; pub use into_stream::*; diff --git a/src/slash_argument/slash_macro.rs b/src/slash_argument/slash_macro.rs index 4b7cb56a21ef..15c419f81b7d 100644 --- a/src/slash_argument/slash_macro.rs +++ b/src/slash_argument/slash_macro.rs @@ -1,7 +1,7 @@ //! Infrastructure to parse received slash command arguments into Rust types. #[allow(unused_imports)] // import is required if serenity simdjson feature is enabled -use crate::serenity::json::prelude::*; +use crate::serenity::json::*; #[allow(unused_imports)] // required for introdoc-links in doc comments use crate::serenity_prelude as serenity; @@ -69,10 +69,7 @@ macro_rules! _parse_slash { // Extract Option ($ctx:ident, $interaction:ident, $args:ident => $name:literal: Option<$type:ty $(,)*>) => { if let Some(arg) = $args.iter().find(|arg| arg.name == $name) { - let arg = arg.value - .as_ref() - .ok_or($crate::SlashArgError::new_command_structure_mismatch("expected argument value"))?; - Some($crate::extract_slash_argument!($type, $ctx, $interaction, arg) + Some($crate::extract_slash_argument!($type, $ctx, $interaction, &arg.value) .await?) } else { None @@ -112,11 +109,11 @@ directly. # #[tokio::main] async fn main() -> Result<(), Box> { # use poise::serenity_prelude as serenity; let ctx: serenity::Context = todo!(); -let interaction: poise::ApplicationCommandOrAutocompleteInteraction = todo!(); -let args: &[serenity::CommandDataOption] = todo!(); +let interaction: serenity::CommandInteraction = todo!(); +let args: &[serenity::ResolvedOption] = todo!(); let (arg1, arg2) = poise::parse_slash_args!( - &ctx, interaction, + &ctx, &interaction, args => ("arg1": String), ("arg2": Option) ).await?; diff --git a/src/slash_argument/slash_trait.rs b/src/slash_argument/slash_trait.rs index 1377e8592bef..a7bcb1bd8e4f 100644 --- a/src/slash_argument/slash_trait.rs +++ b/src/slash_argument/slash_trait.rs @@ -5,7 +5,7 @@ use std::convert::TryInto as _; use std::marker::PhantomData; #[allow(unused_imports)] // import is required if serenity simdjson feature is enabled -use crate::serenity::json::prelude::*; +use crate::serenity::json::*; use crate::serenity_prelude as serenity; /// Implement this trait on types that you want to use as a slash command parameter. @@ -17,8 +17,8 @@ pub trait SlashArgument: Sized { /// Don't call this method directly! Use [`crate::extract_slash_argument!`] async fn extract( ctx: &serenity::Context, - interaction: crate::ApplicationCommandOrAutocompleteInteraction<'_>, - value: &serenity::json::Value, + interaction: &serenity::CommandInteraction, + value: &serenity::ResolvedValue<'_>, ) -> Result; /// Create a slash command parameter equivalent to type T. @@ -27,7 +27,7 @@ pub trait SlashArgument: Sized { /// filling in `name()`, `description()`, and possibly `required()` or other fields. /// /// Don't call this method directly! Use [`crate::create_slash_argument!`] - fn create(builder: &mut serenity::CreateApplicationCommandOption); + fn create(builder: serenity::CreateCommandOption) -> serenity::CreateCommandOption; /// If this is a choice parameter, returns the choices /// @@ -47,11 +47,11 @@ pub trait SlashArgumentHack: Sized { async fn extract( self, ctx: &serenity::Context, - interaction: crate::ApplicationCommandOrAutocompleteInteraction<'_>, - value: &serenity::json::Value, + interaction: &serenity::CommandInteraction, + value: &serenity::ResolvedValue<'_>, ) -> Result; - fn create(self, builder: &mut serenity::CreateApplicationCommandOption); + fn create(self, builder: serenity::CreateCommandOption) -> serenity::CreateCommandOption; fn choices(self) -> Vec { Vec::new() @@ -99,18 +99,22 @@ where async fn extract( self, ctx: &serenity::Context, - interaction: crate::ApplicationCommandOrAutocompleteInteraction<'_>, - value: &serenity::json::Value, + interaction: &serenity::CommandInteraction, + value: &serenity::ResolvedValue<'_>, ) -> Result { - let string = value - .as_str() - .ok_or(SlashArgError::CommandStructureMismatch { - description: "expected string", - })?; + let string = match value { + serenity::ResolvedValue::String(str) => *str, + _ => { + return Err(SlashArgError::CommandStructureMismatch { + description: "expected string", + }) + } + }; + T::convert( ctx, - interaction.guild_id(), - Some(interaction.channel_id()), + interaction.guild_id, + Some(interaction.channel_id), string, ) .await @@ -120,8 +124,8 @@ where }) } - fn create(self, builder: &mut serenity::CreateApplicationCommandOption) { - builder.kind(serenity::CommandOptionType::String); + fn create(self, builder: serenity::CreateCommandOption) -> serenity::CreateCommandOption { + builder.kind(serenity::CommandOptionType::String) } } @@ -132,21 +136,24 @@ macro_rules! impl_for_integer { impl SlashArgument for $t { async fn extract( _: &serenity::Context, - _: crate::ApplicationCommandOrAutocompleteInteraction<'_>, - value: &serenity::json::Value, + _: &serenity::CommandInteraction, + value: &serenity::ResolvedValue<'_>, ) -> Result<$t, SlashArgError> { + let value = match value { + serenity::ResolvedValue::Integer(int) => *int, + _ => return Err(SlashArgError::CommandStructureMismatch { description: "expected integer" }) + }; + value - .as_i64() - .ok_or(SlashArgError::CommandStructureMismatch { description: "expected integer" })? .try_into() .map_err(|_| SlashArgError::CommandStructureMismatch { description: "received out of bounds integer" }) } - fn create(builder: &mut serenity::CreateApplicationCommandOption) { + fn create(builder: serenity::CreateCommandOption) -> serenity::CreateCommandOption { builder .min_number_value(f64::max(<$t>::MIN as f64, -9007199254740991.)) .max_number_value(f64::min(<$t>::MAX as f64, 9007199254740991.)) - .kind(serenity::CommandOptionType::Integer); + .kind(serenity::CommandOptionType::Integer) } } )* }; @@ -161,16 +168,17 @@ macro_rules! impl_for_float { async fn extract( self, _: &serenity::Context, - _: crate::ApplicationCommandOrAutocompleteInteraction<'_>, - value: &serenity::json::Value, + _: &serenity::CommandInteraction, + value: &serenity::ResolvedValue<'_>, ) -> Result<$t, SlashArgError> { - Ok(value - .as_f64() - .ok_or(SlashArgError::CommandStructureMismatch { description: "expected float" })? as $t) + match value { + serenity::ResolvedValue::Number(float) => Ok(*float as $t), + _ => Err(SlashArgError::CommandStructureMismatch { description: "expected float" }) + } } - fn create(self, builder: &mut serenity::CreateApplicationCommandOption) { - builder.kind(serenity::CommandOptionType::Number); + fn create(self, builder: serenity::CreateCommandOption) -> serenity::CreateCommandOption { + builder.kind(serenity::CommandOptionType::Number) } } )* }; @@ -182,18 +190,19 @@ impl SlashArgumentHack for &PhantomData { async fn extract( self, _: &serenity::Context, - _: crate::ApplicationCommandOrAutocompleteInteraction<'_>, - value: &serenity::json::Value, + _: &serenity::CommandInteraction, + value: &serenity::ResolvedValue<'_>, ) -> Result { - Ok(value - .as_bool() - .ok_or(SlashArgError::CommandStructureMismatch { + match value { + serenity::ResolvedValue::Boolean(val) => Ok(*val), + _ => Err(SlashArgError::CommandStructureMismatch { description: "expected bool", - })?) + }), + } } - fn create(self, builder: &mut serenity::CreateApplicationCommandOption) { - builder.kind(serenity::CommandOptionType::Boolean); + fn create(self, builder: serenity::CreateCommandOption) -> serenity::CreateCommandOption { + builder.kind(serenity::CommandOptionType::Boolean) } } @@ -202,23 +211,25 @@ impl SlashArgumentHack for &PhantomData, - value: &Value, + interaction: &serenity::CommandInteraction, + value: &serenity::ResolvedValue<'_>, ) -> Result { - let attachment_id = serenity::AttachmentId( - value - .as_str() - .ok_or(SlashArgError::CommandStructureMismatch { + let attachment_id = match value { + serenity::ResolvedValue::String(val) => { + val.parse() + .map_err(|_| SlashArgError::CommandStructureMismatch { + description: "improper attachment id passed", + })? + } + _ => { + return Err(SlashArgError::CommandStructureMismatch { description: "expected attachment id", - })? - .parse() - .map_err(|_| SlashArgError::CommandStructureMismatch { - description: "improper attachment id passed", - })?, - ); + }) + } + }; interaction - .data() + .data .resolved .attachments .get(&attachment_id) @@ -228,8 +239,8 @@ impl SlashArgumentHack for &PhantomData serenity::CreateCommandOption { + builder.kind(serenity::CommandOptionType::Attachment) } } @@ -238,14 +249,14 @@ impl SlashArgumentHack for &PhantomData { async fn extract( self, ctx: &serenity::Context, - interaction: crate::ApplicationCommandOrAutocompleteInteraction<'_>, - value: &serenity::json::Value, + interaction: &serenity::CommandInteraction, + value: &serenity::ResolvedValue<'_>, ) -> Result { ::extract(ctx, interaction, value).await } - fn create(self, builder: &mut serenity::CreateApplicationCommandOption) { - ::create(builder); + fn create(self, builder: serenity::CreateCommandOption) -> serenity::CreateCommandOption { + ::create(builder) } fn choices(self) -> Vec { @@ -260,15 +271,15 @@ macro_rules! impl_slash_argument { impl SlashArgument for $type { async fn extract( ctx: &serenity::Context, - interaction: crate::ApplicationCommandOrAutocompleteInteraction<'_>, - value: &serenity::json::Value, + interaction: &serenity::CommandInteraction, + value: &serenity::ResolvedValue<'_>, ) -> Result<$type, SlashArgError> { // We can parse IDs by falling back to the generic serenity::ArgumentConvert impl PhantomData::<$type>.extract(ctx, interaction, value).await } - fn create(builder: &mut serenity::CreateApplicationCommandOption) { - builder.kind(serenity::CommandOptionType::$slash_param_type); + fn create(builder: serenity::CreateCommandOption) -> serenity::CreateCommandOption { + builder.kind(serenity::CommandOptionType::$slash_param_type) } } }; diff --git a/src/structs/command.rs b/src/structs/command.rs index 70ace23d48a7..453a4840151f 100644 --- a/src/structs/command.rs +++ b/src/structs/command.rs @@ -138,34 +138,35 @@ impl Eq for Command {} impl Command { /// Serializes this Command into an application command option, which is the form which Discord /// requires subcommands to be in - fn create_as_subcommand(&self) -> Option { + fn create_as_subcommand(&self) -> Option { self.slash_action?; - let mut builder = serenity::CreateApplicationCommandOption::default(); - builder - .name(&self.name) - .description(self.description.as_deref().unwrap_or("A slash command")); + let kind = if self.subcommands.is_empty() { + serenity::CommandOptionType::SubCommand + } else { + serenity::CommandOptionType::SubCommandGroup + }; + + let description = self.description.as_deref().unwrap_or("A slash command"); + let mut builder = serenity::CreateCommandOption::new(kind, self.name.clone(), description); + for (locale, name) in &self.name_localizations { - builder.name_localized(locale, name); + builder = builder.name_localized(locale, name); } for (locale, description) in &self.description_localizations { - builder.description_localized(locale, description); + builder = builder.description_localized(locale, description); } if self.subcommands.is_empty() { - builder.kind(serenity::CommandOptionType::SubCommand); - for param in &self.parameters { // Using `?` because if this command has slash-incompatible parameters, we cannot // just ignore them but have to abort the creation process entirely - builder.add_sub_option(param.create_as_slash_command_option()?); + builder = builder.add_sub_option(param.create_as_slash_command_option()?); } } else { - builder.kind(serenity::CommandOptionType::SubCommandGroup); - for subcommand in &self.subcommands { if let Some(subcommand) = subcommand.create_as_subcommand() { - builder.add_sub_option(subcommand); + builder = builder.add_sub_option(subcommand); } } } @@ -175,40 +176,39 @@ impl Command { /// Generates a slash command builder from this [`Command`] instance. This can be used /// to register this command on Discord's servers - pub fn create_as_slash_command(&self) -> Option { + pub fn create_as_slash_command(&self) -> Option { self.slash_action?; - let mut builder = serenity::CreateApplicationCommand::default(); - builder - .name(&self.name) + let mut builder = serenity::CreateCommand::new(self.name.clone()) .description(self.description.as_deref().unwrap_or("A slash command")); + for (locale, name) in &self.name_localizations { - builder.name_localized(locale, name); + builder = builder.name_localized(locale, name); } for (locale, description) in &self.description_localizations { - builder.description_localized(locale, description); + builder = builder.description_localized(locale, description); } // This is_empty check is needed because Discord special cases empty // default_member_permissions to mean "admin-only" (yes it's stupid) if !self.default_member_permissions.is_empty() { - builder.default_member_permissions(self.default_member_permissions); + builder = builder.default_member_permissions(self.default_member_permissions); } if self.guild_only { - builder.dm_permission(false); + builder = builder.dm_permission(false); } if self.subcommands.is_empty() { for param in &self.parameters { // Using `?` because if this command has slash-incompatible parameters, we cannot // just ignore them but have to abort the creation process entirely - builder.add_option(param.create_as_slash_command_option()?); + builder = builder.add_option(param.create_as_slash_command_option()?); } } else { for subcommand in &self.subcommands { if let Some(subcommand) = subcommand.create_as_subcommand() { - builder.add_option(subcommand); + builder = builder.add_option(subcommand); } } } @@ -218,42 +218,21 @@ impl Command { /// Generates a context menu command builder from this [`Command`] instance. This can be used /// to register this command on Discord's servers - pub fn create_as_context_menu_command(&self) -> Option { + pub fn create_as_context_menu_command(&self) -> Option { let context_menu_action = self.context_menu_action?; - let mut builder = serenity::CreateApplicationCommand::default(); - builder - // TODO: localization? - .name(self.context_menu_name.as_deref().unwrap_or(&self.name)) - .kind(match context_menu_action { - crate::ContextMenuCommandAction::User(_) => serenity::CommandType::User, - crate::ContextMenuCommandAction::Message(_) => serenity::CommandType::Message, - crate::ContextMenuCommandAction::__NonExhaustive => unreachable!(), - }); + // TODO: localization? + let name = self.context_menu_name.as_deref().unwrap_or(&self.name); + let mut builder = serenity::CreateCommand::new(name).kind(match context_menu_action { + crate::ContextMenuCommandAction::User(_) => serenity::CommandType::User, + crate::ContextMenuCommandAction::Message(_) => serenity::CommandType::Message, + crate::ContextMenuCommandAction::__NonExhaustive => unreachable!(), + }); if self.guild_only { - builder.dm_permission(false); + builder = builder.dm_permission(false); } Some(builder) } - - /// **Deprecated** - #[deprecated = "Please use `poise::Command { category: \"...\", ..command() }` instead"] - pub fn category(&mut self, category: String) -> &mut Self { - self.category = Some(category); - self - } - - /// Insert a subcommand - #[deprecated = "Please use `poise::Command { subcommands: vec![...], ..command() }` instead"] - pub fn subcommand( - &mut self, - mut subcommand: crate::Command, - meta_builder: impl FnOnce(&mut Self) -> &mut Self, - ) -> &mut Self { - meta_builder(&mut subcommand); - self.subcommands.push(subcommand); - self - } } diff --git a/src/structs/context.rs b/src/structs/context.rs index 1548681edae6..72f6ccf07ae2 100644 --- a/src/structs/context.rs +++ b/src/structs/context.rs @@ -2,7 +2,7 @@ use std::borrow::Cow; -use crate::serenity_prelude as serenity; +use crate::{serenity_prelude as serenity, CommandInteractionType}; // needed for proc macro #[doc(hidden)] @@ -116,7 +116,7 @@ context_methods! { Self::Prefix(ctx) => Some( ctx.msg .channel_id - .start_typing(&ctx.serenity_context.http)?, + .start_typing(&ctx.serenity_context.http), ), }) } @@ -155,7 +155,7 @@ context_methods! { await (send self builder) (pub async fn send<'att>( self, - builder: crate::CreateReply<'att>, + builder: crate::CreateReply, ) -> Result, serenity::Error>) { crate::send_reply(self, builder).await } @@ -209,7 +209,7 @@ context_methods! { (channel_id self) (pub fn channel_id(self) -> serenity::ChannelId) { match self { - Self::Application(ctx) => ctx.interaction.channel_id(), + Self::Application(ctx) => ctx.interaction.channel_id, Self::Prefix(ctx) => ctx.msg.channel_id, } } @@ -218,7 +218,7 @@ context_methods! { (guild_id self) (pub fn guild_id(self) -> Option) { match self { - Self::Application(ctx) => ctx.interaction.guild_id(), + Self::Application(ctx) => ctx.interaction.guild_id, Self::Prefix(ctx) => ctx.msg.guild_id, } } @@ -235,12 +235,10 @@ context_methods! { // Doesn't fit in with the rest of the functions here but it's convenient /// Return the guild of this context, if we are inside a guild. - /// - /// Warning: clones the entire Guild instance out of the cache #[cfg(feature = "cache")] (guild self) - (pub fn guild(self) -> Option) { - self.guild_id()?.to_guild_cached(self) + (pub fn guild(self) -> Option>) { + self.guild_id()?.to_guild_cached(self.serenity_context()) } // Doesn't fit in with the rest of the functions here but it's convenient @@ -253,11 +251,11 @@ context_methods! { await (partial_guild self) (pub async fn partial_guild(self) -> Option) { #[cfg(feature = "cache")] - if let Some(guild) = self.guild_id()?.to_guild_cached(self) { - return Some(guild.into()); + if let Some(guild) = self.guild() { + return Some(guild.clone().into()); } - self.guild_id()?.to_partial_guild(self).await.ok() + self.guild_id()?.to_partial_guild(self.serenity_context()).await.ok() } // Doesn't fit in with the rest of the functions here but it's convenient @@ -273,7 +271,7 @@ context_methods! { await (author_member self) (pub async fn author_member(self) -> Option>) { if let Self::Application(ctx) = self { - ctx.interaction.member().map(Cow::Borrowed) + ctx.interaction.member.as_deref().map(Cow::Borrowed) } else { self.guild_id()? .member(self.serenity_context(), self.author().id) @@ -287,7 +285,7 @@ context_methods! { (created_at self) (pub fn created_at(self) -> serenity::Timestamp) { match self { - Self::Application(ctx) => ctx.interaction.id().created_at(), + Self::Application(ctx) => ctx.interaction.id.created_at(), Self::Prefix(ctx) => ctx.msg.timestamp, } } @@ -296,7 +294,7 @@ context_methods! { (author self) (pub fn author(self) -> &'a serenity::User) { match self { - Self::Application(ctx) => ctx.interaction.user(), + Self::Application(ctx) => &ctx.interaction.user, Self::Prefix(ctx) => &ctx.msg.author, } } @@ -306,9 +304,9 @@ context_methods! { (id self) (pub fn id(self) -> u64) { match self { - Self::Application(ctx) => ctx.interaction.id().0, + Self::Application(ctx) => ctx.interaction.id.get(), Self::Prefix(ctx) => { - let mut id = ctx.msg.id.0; + let mut id = ctx.msg.id.get(); if let Some(edited_timestamp) = ctx.msg.edited_timestamp { // We replace the 42 datetime bits with msg.timestamp_edited so that the ID is // unique even after edits @@ -371,7 +369,7 @@ context_methods! { (pub fn invoked_command_name(self) -> &'a str) { match self { Self::Prefix(ctx) => ctx.invoked_command_name, - Self::Application(ctx) => &ctx.interaction.data().name, + Self::Application(ctx) => &ctx.interaction.data.name, } } @@ -407,26 +405,48 @@ context_methods! { } string += &ctx.command.name; for arg in ctx.args { - if let Some(value) = &arg.value { - #[allow(unused_imports)] // required for simd-json - use ::serenity::json::prelude::*; - use std::fmt::Write as _; - - string += " "; - string += &arg.name; - string += ":"; - if let Some(x) = value.as_bool() { - let _ = write!(string, "{}", x); - } else if let Some(x) = value.as_i64() { - let _ = write!(string, "{}", x); - } else if let Some(x) = value.as_u64() { - let _ = write!(string, "{}", x); - } else if let Some(x) = value.as_f64() { - let _ = write!(string, "{}", x); - } else if let Some(x) = value.as_str() { - let _ = write!(string, "{}", x); + #[allow(unused_imports)] // required for simd-json + use ::serenity::json::*; + use std::fmt::Write as _; + + string += " "; + string += &arg.name; + string += ":"; + + let _ = match arg.value { + // This was verified to match Discord behavior when copy-pasting a not-yet + // sent slash command invocation + serenity::ResolvedValue::Attachment(_) => write!(string, ""), + serenity::ResolvedValue::Boolean(x) => write!(string, "{}", x), + serenity::ResolvedValue::Integer(x) => write!(string, "{}", x), + serenity::ResolvedValue::Number(x) => write!(string, "{}", x), + serenity::ResolvedValue::String(x) => write!(string, "{}", x), + serenity::ResolvedValue::Channel(x) => { + write!(string, "#{}", x.name.as_deref().unwrap_or("")) + } + serenity::ResolvedValue::Role(x) => write!(string, "@{}", x.name), + serenity::ResolvedValue::User(x, _) => { + string.push('@'); + string.push_str(&x.name); + if let Some(discrim) = x.discriminator { + let _ = write!(string, "#{discrim:04}"); + } + Ok(()) } - } + + serenity::ResolvedValue::Unresolved(_) + | serenity::ResolvedValue::SubCommand(_) + | serenity::ResolvedValue::SubCommandGroup(_) + | serenity::ResolvedValue::Autocomplete { .. } => { + log::warn!("unexpected interaction option type"); + Ok(()) + } + // We need this because ResolvedValue is #[non_exhaustive] + _ => { + log::warn!("newly-added unknown interaction option type"); + Ok(()) + } + }; } string } @@ -461,7 +481,7 @@ context_methods! { (locale self) (pub fn locale(self) -> Option<&'a str>) { match self { - Context::Application(ctx) => Some(ctx.interaction.locale()), + Context::Application(ctx) => Some(&ctx.interaction.locale), Context::Prefix(_) => None, } } @@ -471,12 +491,13 @@ context_methods! { /// /// This is primarily an internal function and only exposed for people who want to manually /// convert [`crate::CreateReply`] instances into Discord requests. + #[allow(unused_mut)] // side effect of how macro works (reply_builder self builder) - (pub fn reply_builder<'att>(self, mut builder: crate::CreateReply<'att>) -> crate::CreateReply<'att>) { + (pub fn reply_builder(self, mut builder: crate::CreateReply) -> crate::CreateReply) { builder.allowed_mentions = builder.allowed_mentions.or_else(|| self.framework().options().allowed_mentions.clone()); if let Some(callback) = self.framework().options().reply_callback { - callback(self, &mut builder); + builder = callback(self, builder); } builder @@ -504,7 +525,7 @@ context_methods! { /// If the shard has just connected, this value is zero. await (ping self) (pub async fn ping(self) -> std::time::Duration) { - match self.framework().shard_manager.lock().await.runners.lock().await.get(&serenity::ShardId(self.serenity_context().shard_id)) { + match self.framework().shard_manager.runners.lock().await.get(&self.serenity_context().shard_id) { Some(runner) => runner.latency.unwrap_or(std::time::Duration::ZERO), None => { log::error!("current shard is not in shard_manager.runners, this shouldn't happen"); @@ -520,17 +541,12 @@ impl<'a, U, E> Context<'a, U, E> { match self { Self::Application(ctx) => { // Skip autocomplete interactions - let interaction = match ctx.interaction { - crate::ApplicationCommandOrAutocompleteInteraction::ApplicationCommand( - interaction, - ) => interaction, - crate::ApplicationCommandOrAutocompleteInteraction::Autocomplete(_) => { - return Ok(()) - } - }; + if ctx.interaction_type == CommandInteractionType::Autocomplete { + return Ok(()); + } // Check slash command - if interaction.data.kind == serenity::CommandType::ChatInput { + if ctx.interaction.data.kind == serenity::CommandType::ChatInput { return if let Some(action) = ctx.command.slash_action { action(ctx).await } else { @@ -539,20 +555,21 @@ impl<'a, U, E> Context<'a, U, E> { } // Check context menu command - if let (Some(action), Some(target)) = - (ctx.command.context_menu_action, &interaction.data.target()) - { + if let (Some(action), Some(target)) = ( + ctx.command.context_menu_action, + &ctx.interaction.data.target(), + ) { return match action { crate::ContextMenuCommandAction::User(action) => { if let serenity::ResolvedTarget::User(user, _) = target { - action(ctx, user.clone()).await + action(ctx, (*user).clone()).await } else { Ok(()) } } crate::ContextMenuCommandAction::Message(action) => { if let serenity::ResolvedTarget::Message(message) = target { - action(ctx, *message.clone()).await + action(ctx, (*message).clone()).await } else { Ok(()) } @@ -610,7 +627,7 @@ macro_rules! context_trait_impls { self.serenity_context() } } - impl serenity::CacheHttp for $($type)*<'_, U, E> { + impl serenity::CacheHttp for $($type)*<'_, U, E> { fn http(&self) -> &serenity::Http { &self.serenity_context().http } diff --git a/src/structs/framework_error.rs b/src/structs/framework_error.rs index 58da62f9c16c..dd968c2887dd 100644 --- a/src/structs/framework_error.rs +++ b/src/structs/framework_error.rs @@ -28,11 +28,8 @@ pub enum FrameworkError<'a, U, E> { EventHandler { /// Error which was thrown in the event handler code error: E, - /// The serenity Context passed to the event - #[derivative(Debug = "ignore")] - ctx: &'a serenity::Context, /// Which event was being processed when the error occurred - event: &'a crate::Event<'a>, + event: &'a serenity::FullEvent, /// The Framework passed to the event #[derivative(Debug = "ignore")] framework: crate::FrameworkContext<'a, U, E>, @@ -195,7 +192,7 @@ pub enum FrameworkError<'a, U, E> { #[derivative(Debug = "ignore")] framework: crate::FrameworkContext<'a, U, E>, /// The interaction in question - interaction: crate::ApplicationCommandOrAutocompleteInteraction<'a>, + interaction: &'a serenity::CommandInteraction, }, // #[non_exhaustive] forbids struct update syntax for ?? reason #[doc(hidden)] @@ -203,31 +200,6 @@ pub enum FrameworkError<'a, U, E> { } impl<'a, U, E> FrameworkError<'a, U, E> { - /// Returns the [`serenity::Context`] of this error - pub fn serenity_context(&self) -> &'a serenity::Context { - match *self { - Self::Setup { ctx, .. } => ctx, - Self::EventHandler { ctx, .. } => ctx, - Self::Command { ctx, .. } => ctx.serenity_context(), - Self::SubcommandRequired { ctx } => ctx.serenity_context(), - Self::CommandPanic { ctx, .. } => ctx.serenity_context(), - Self::ArgumentParse { ctx, .. } => ctx.serenity_context(), - Self::CommandStructureMismatch { ctx, .. } => ctx.serenity_context, - Self::CooldownHit { ctx, .. } => ctx.serenity_context(), - Self::MissingBotPermissions { ctx, .. } => ctx.serenity_context(), - Self::MissingUserPermissions { ctx, .. } => ctx.serenity_context(), - Self::NotAnOwner { ctx, .. } => ctx.serenity_context(), - Self::GuildOnly { ctx, .. } => ctx.serenity_context(), - Self::DmOnly { ctx, .. } => ctx.serenity_context(), - Self::NsfwOnly { ctx, .. } => ctx.serenity_context(), - Self::CommandCheckFailed { ctx, .. } => ctx.serenity_context(), - Self::DynamicPrefix { ctx, .. } => ctx.serenity_context, - Self::UnknownCommand { ctx, .. } => ctx, - Self::UnknownInteraction { ctx, .. } => ctx, - Self::__NonExhaustive(unreachable) => match unreachable {}, - } - } - /// Returns the [`crate::Context`] of this error, if it has one pub fn ctx(&self) -> Option> { Some(match *self { @@ -304,10 +276,13 @@ impl std::fmt::Display for FrameworkError<'_, U, E> { } => write!(f, "poise setup error"), Self::EventHandler { error: _, - ctx: _, event, framework: _, - } => write!(f, "error in {} event event handler", event.name()), + } => write!( + f, + "error in {} event event handler", + event.snake_case_name() + ), Self::Command { error: _, ctx } => { write!(f, "error in command `{}`", full_command_name!(ctx)) } @@ -404,7 +379,7 @@ impl std::fmt::Display for FrameworkError<'_, U, E> { write!(f, "unknown command `{}`", msg_content) } Self::UnknownInteraction { interaction, .. } => { - write!(f, "unknown interaction `{}`", interaction.data().name) + write!(f, "unknown interaction `{}`", interaction.data.name) } Self::__NonExhaustive(unreachable) => match *unreachable {}, } diff --git a/src/structs/framework_options.rs b/src/structs/framework_options.rs index 92663bae2ca0..bcd7246f5990 100644 --- a/src/structs/framework_options.rs +++ b/src/structs/framework_options.rs @@ -33,7 +33,8 @@ pub struct FrameworkOptions { /// /// Allows you to modify every outgoing message in a central place #[derivative(Debug = "ignore")] - pub reply_callback: Option, &mut crate::CreateReply<'_>)>, + pub reply_callback: + Option, crate::CreateReply) -> crate::CreateReply>, /// If `true`, disables automatic cooldown handling before every command invocation. /// /// Useful for implementing custom cooldown behavior. See [`crate::Command::cooldowns`] and @@ -48,8 +49,7 @@ pub struct FrameworkOptions { /// deletions or guild updates. #[derivative(Debug = "ignore")] pub event_handler: for<'a> fn( - &'a serenity::Context, - &'a crate::Event<'a>, + &'a serenity::FullEvent, crate::FrameworkContext<'a, U, E>, // TODO: redundant with framework &'a U, @@ -60,10 +60,12 @@ pub struct FrameworkOptions { /// Prefix command specific options. pub prefix_options: crate::PrefixFrameworkOptions, /// User IDs which are allowed to use owners_only commands - /// - /// If using [`crate::FrameworkBuilder`], automatically initialized with the bot application - /// owner and team members pub owners: std::collections::HashSet, + /// If true, [`Self::owners`] is automatically initialized with the results of + /// [`serenity::Http::get_current_application_info()`]. + /// + /// True by default. + pub initialize_owners: bool, // #[non_exhaustive] forbids struct update syntax for ?? reason #[doc(hidden)] pub __non_exhaustive: (), @@ -98,26 +100,25 @@ where } }) }, - event_handler: |_, _, _, _| Box::pin(async { Ok(()) }), + event_handler: |_, _, _| Box::pin(async { Ok(()) }), listener: (), pre_command: |_| Box::pin(async {}), post_command: |_| Box::pin(async {}), command_check: None, skip_checks_for_owners: false, - allowed_mentions: Some({ - let mut f = serenity::CreateAllowedMentions::default(); + allowed_mentions: Some( // Only support direct user pings by default - f.empty_parse() - .parse(serenity::ParseValue::Users) + serenity::CreateAllowedMentions::default() + .all_users(true) // https://github.com/serenity-rs/poise/issues/176 - .replied_user(true); - f - }), + .replied_user(true), + ), reply_callback: None, manual_cooldowns: false, require_cache_for_guild_check: false, prefix_options: Default::default(), owners: Default::default(), + initialize_owners: true, __non_exhaustive: (), } } diff --git a/src/structs/prefix.rs b/src/structs/prefix.rs index d4315b654915..f80561a1e4e2 100644 --- a/src/structs/prefix.rs +++ b/src/structs/prefix.rs @@ -128,7 +128,7 @@ pub struct PrefixFrameworkOptions { pub mention_as_prefix: bool, /// If Some, the framework will react to message edits by editing the corresponding bot response /// with the new result. - pub edit_tracker: Option>, + pub edit_tracker: Option>>, /// If the user makes a typo in their message and a subsequent edit creates a valid invocation, /// the bot will execute the command if this attribute is set. [`Self::edit_tracker`] does not /// need to be set for this. diff --git a/src/structs/slash.rs b/src/structs/slash.rs index e430c2c8edf7..9419bc923437 100644 --- a/src/structs/slash.rs +++ b/src/structs/slash.rs @@ -2,87 +2,13 @@ use crate::{serenity_prelude as serenity, BoxFuture}; -/// Abstracts over a refernce to an application command interaction or autocomplete interaction -/// -/// Used in [`crate::ApplicationContext`]. We need to support autocomplete interactions in -/// [`crate::ApplicationContext`] because command checks are invoked for autocomplete interactions -/// too: we don't want poise accidentally leaking sensitive information through autocomplete -/// suggestions -#[derive(Copy, Clone, Debug)] -pub enum ApplicationCommandOrAutocompleteInteraction<'a> { - /// An application command interaction - ApplicationCommand(&'a serenity::ApplicationCommandInteraction), - /// An autocomplete interaction - Autocomplete(&'a serenity::AutocompleteInteraction), - // Not non_exhaustive, this type is deliberately just two possible variants -} - -impl<'a> ApplicationCommandOrAutocompleteInteraction<'a> { - /// Returns the data field of the underlying interaction - pub fn data(self) -> &'a serenity::CommandData { - match self { - Self::ApplicationCommand(x) => &x.data, - Self::Autocomplete(x) => &x.data, - } - } - - /// Returns the ID of the underlying interaction - pub fn id(self) -> serenity::InteractionId { - match self { - Self::ApplicationCommand(x) => x.id, - Self::Autocomplete(x) => x.id, - } - } - - /// Returns the guild ID of the underlying interaction - pub fn guild_id(self) -> Option { - match self { - Self::ApplicationCommand(x) => x.guild_id, - Self::Autocomplete(x) => x.guild_id, - } - } - - /// Returns the channel ID of the underlying interaction - pub fn channel_id(self) -> serenity::ChannelId { - match self { - Self::ApplicationCommand(x) => x.channel_id, - Self::Autocomplete(x) => x.channel_id, - } - } - - /// Returns the member field of the underlying interaction - pub fn member(self) -> Option<&'a serenity::Member> { - match self { - Self::ApplicationCommand(x) => x.member.as_ref(), - Self::Autocomplete(x) => x.member.as_ref(), - } - } - - /// Returns the user field of the underlying interaction - pub fn user(self) -> &'a serenity::User { - match self { - Self::ApplicationCommand(x) => &x.user, - Self::Autocomplete(x) => &x.user, - } - } - - /// Returns the inner [`serenity::ApplicationCommandInteraction`] and panics otherwise - pub fn unwrap(self) -> &'a serenity::ApplicationCommandInteraction { - match self { - ApplicationCommandOrAutocompleteInteraction::ApplicationCommand(x) => x, - ApplicationCommandOrAutocompleteInteraction::Autocomplete(_) => { - panic!("expected application command interaction, got autocomplete interaction") - } - } - } - - /// Returns the locale field of the underlying interaction - pub fn locale(self) -> &'a str { - match self { - ApplicationCommandOrAutocompleteInteraction::ApplicationCommand(x) => &x.locale, - ApplicationCommandOrAutocompleteInteraction::Autocomplete(x) => &x.locale, - } - } +/// Specifies if the current invokation is from a Command or Autocomplete. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum CommandInteractionType { + /// Invoked from an application command + Command, + /// Invoked from an autocomplete interaction + Autocomplete, } /// Application command specific context passed to command invocations. @@ -93,13 +19,15 @@ pub struct ApplicationContext<'a, U, E> { #[derivative(Debug = "ignore")] pub serenity_context: &'a serenity::Context, /// The interaction which triggered this command execution. - pub interaction: ApplicationCommandOrAutocompleteInteraction<'a>, + pub interaction: &'a serenity::CommandInteraction, + /// The type of the interaction which triggered this command execution. + pub interaction_type: CommandInteractionType, /// Slash command arguments /// /// **Not** equivalent to `self.interaction.data().options`. That one refers to just the /// top-level command arguments, whereas [`Self::args`] is the options of the actual /// subcommand, if any. - pub args: &'a [serenity::CommandDataOption], + pub args: &'a [serenity::ResolvedOption<'a>], /// Keeps track of whether an initial response has been sent. /// /// Discord requires different HTTP endpoints for initial and additional responses. @@ -137,21 +65,18 @@ impl crate::_GetGenerics for ApplicationContext<'_, U, E> { impl ApplicationContext<'_, U, E> { /// See [`crate::Context::defer()`] pub async fn defer_response(&self, ephemeral: bool) -> Result<(), serenity::Error> { - let interaction = match self.interaction { - ApplicationCommandOrAutocompleteInteraction::ApplicationCommand(x) => x, - ApplicationCommandOrAutocompleteInteraction::Autocomplete(_) => return Ok(()), - }; - if !self .has_sent_initial_response .load(std::sync::atomic::Ordering::SeqCst) { - interaction - .create_interaction_response(self.serenity_context, |f| { - f.kind(serenity::InteractionResponseType::DeferredChannelMessageWithSource) - .interaction_response_data(|b| b.ephemeral(ephemeral)) - }) + let response = serenity::CreateInteractionResponse::Defer( + serenity::CreateInteractionResponseMessage::new().ephemeral(ephemeral), + ); + + self.interaction + .create_response(self.serenity_context, response) .await?; + self.has_sent_initial_response .store(true, std::sync::atomic::Ordering::SeqCst); } @@ -225,12 +150,12 @@ pub struct CommandParameter { /// For example a u32 [`CommandParameter`] would store this as the [`Self::type_setter`]: /// ```rust /// # use poise::serenity_prelude as serenity; - /// # let _: fn(&mut serenity::CreateApplicationCommandOption) -> &mut serenity::CreateApplicationCommandOption = - /// |b| b.kind(serenity::CommandOptionType::Integer).min_int_value(0).max_int_value(u32::MAX) + /// # let _: fn(serenity::CreateCommandOption) -> serenity::CreateCommandOption = + /// |b| b.kind(serenity::CommandOptionType::Integer).min_int_value(0).max_int_value(u64::MAX) /// # ; /// ``` #[derivative(Debug = "ignore")] - pub type_setter: Option, + pub type_setter: Option serenity::CreateCommandOption>, /// Optionally, a callback that is invoked on autocomplete interactions. This closure should /// extract the partial argument from the given JSON value and generate the autocomplete /// response which contains the list of autocomplete suggestions. @@ -251,32 +176,36 @@ pub struct CommandParameter { impl CommandParameter { /// Generates a slash command parameter builder from this [`CommandParameter`] instance. This /// can be used to register the command on Discord's servers - pub fn create_as_slash_command_option( - &self, - ) -> Option { - let mut builder = serenity::CreateApplicationCommandOption::default(); - builder + pub fn create_as_slash_command_option(&self) -> Option { + let description = self + .description + .as_deref() + .unwrap_or("A slash command parameter"); + + let mut builder = serenity::CreateCommandOption::new( + serenity::CommandOptionType::String, + self.name.clone(), + description, + ); + + builder = builder .required(self.required) - .name(&self.name) - .description( - self.description - .as_deref() - .unwrap_or("A slash command parameter"), - ) .set_autocomplete(self.autocomplete_callback.is_some()); + for (locale, name) in &self.name_localizations { - builder.name_localized(locale, name); + builder = builder.name_localized(locale, name); } for (locale, description) in &self.description_localizations { - builder.description_localized(locale, description); + builder = builder.description_localized(locale, description); } - if let Some(channel_types) = &self.channel_types { - builder.channel_types(channel_types); + if let Some(channel_types) = self.channel_types.clone() { + builder = builder.channel_types(channel_types); } for (i, choice) in self.choices.iter().enumerate() { - builder.add_int_choice_localized(&choice.name, i as _, choice.localizations.iter()); + builder = + builder.add_int_choice_localized(&choice.name, i as _, choice.localizations.iter()); } - (self.type_setter?)(&mut builder); - Some(builder) + + Some((self.type_setter?)(builder)) } }