diff --git a/Cargo.lock b/Cargo.lock index 1c04e94..62d8d9a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -135,7 +135,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", ] [[package]] @@ -151,15 +151,18 @@ dependencies = [ [[package]] name = "autocfg" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" +checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78" +dependencies = [ + "autocfg 1.1.0", +] [[package]] name = "autocfg" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" @@ -270,9 +273,9 @@ checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cc" -version = "1.0.72" +version = "1.0.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] name = "cfg-if" @@ -357,9 +360,10 @@ dependencies = [ "aes-gcm", "base64", "hkdf", + "hmac", "percent-encoding", - "rand 0.8.4", - "sha2 0.10.1", + "rand 0.8.5", + "sha2 0.10.2", "subtle", "time 0.3.7", "version_check", @@ -367,9 +371,9 @@ dependencies = [ [[package]] name = "core-foundation" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ "core-foundation-sys", "libc", @@ -407,28 +411,18 @@ checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" [[package]] name = "crc32fast" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils", -] - [[package]] name = "crossbeam-queue" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b979d76c9fcb84dffc80a73f7290da0f83e4c95773494674cb44b76d13a7a110" +checksum = "4dd435b205a4842da59efd07628f921c096bc1cc0a156835b4fa0bcb9a19bcce" dependencies = [ "cfg-if 1.0.0", "crossbeam-utils", @@ -436,9 +430,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" +checksum = "b5e5bed1f1c269533fa816a0a5492b3545209a205ca1a54842be180eb63a16a6" dependencies = [ "cfg-if 1.0.0", "lazy_static", @@ -457,11 +451,12 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ "generic-array 0.14.5", + "typenum", ] [[package]] @@ -474,14 +469,48 @@ dependencies = [ ] [[package]] -name = "dashmap" -version = "5.0.0" +name = "darling" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b799062aaf67eb976af3bdca031ee6f846d2f0a5710ddbb0d2efee33f3cc4760" +checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dashmap" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" dependencies = [ "cfg-if 1.0.0", "num_cpus", - "parking_lot", "serde", ] @@ -554,9 +583,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer 0.10.2", "crypto-common", @@ -718,9 +747,9 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] name = "futures" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -732,9 +761,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -742,9 +771,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-intrusive" @@ -754,32 +783,32 @@ checksum = "62007592ac46aa7c2b6416f7deb9a8a8f63a01e0f1d6e1787d5630170db2b63e" dependencies = [ "futures-core", "lock_api", - "parking_lot", + "parking_lot 0.11.2", ] [[package]] name = "futures-io" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-sink" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-util" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures-channel", "futures-core", @@ -903,7 +932,7 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util", + "tokio-util 0.6.9", "tracing", ] @@ -951,20 +980,20 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "158bc31e00a68e380286904cc598715f861f2b0ccf7aa6fe20c6d0c49ca5d0f6" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" dependencies = [ "hmac", ] [[package]] name = "hmac" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddca131f3e7f2ce2df364b57949a9d47915cfbd35e46cfee355ccebbf794d6a2" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.2", + "digest 0.10.3", ] [[package]] @@ -975,7 +1004,7 @@ checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" dependencies = [ "bytes", "fnv", - "itoa 1.0.1", + "itoa", ] [[package]] @@ -991,9 +1020,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" +checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" [[package]] name = "httpdate" @@ -1015,9 +1044,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.16" +version = "0.14.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" +checksum = "043f0e083e9901b6cc658a77d1eb86f4fc650bbb977a4337dd63192826aa85dd" dependencies = [ "bytes", "futures-channel", @@ -1028,7 +1057,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 0.4.8", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -1045,7 +1074,7 @@ checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" dependencies = [ "http", "hyper", - "rustls 0.20.2", + "rustls 0.20.4", "tokio", "tokio-rustls 0.23.2", ] @@ -1063,6 +1092,12 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.2.3" @@ -1098,7 +1133,7 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "hashbrown", "serde", ] @@ -1162,12 +1197,6 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.1" @@ -1216,15 +1245,15 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.116" +version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74" +checksum = "06e509672465a0504304aa87f9f176f2b2b716ed8fb105ebe5c02dc6dce96a94" [[package]] name = "libm" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" +checksum = "33a33a362ce288760ec6a508b94caaec573ae7d3bbbd91b87aa0bad4456839db" [[package]] name = "lock_api" @@ -1294,9 +1323,9 @@ checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "mime_guess" -version = "2.0.3" +version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" dependencies = [ "mime", "unicase", @@ -1315,7 +1344,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" dependencies = [ "adler", - "autocfg 1.0.1", + "autocfg 1.1.0", ] [[package]] @@ -1339,9 +1368,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.14" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "ba272f85fa0b41fc91872be579b3bbe0f56b792aa361a380eb669469f68dafb2" dependencies = [ "libc", "log", @@ -1399,7 +1428,7 @@ dependencies = [ "mime", "spin 0.9.2", "tokio", - "tokio-util", + "tokio-util 0.6.9", "version_check", ] @@ -1472,9 +1501,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" dependencies = [ "winapi 0.3.9", ] @@ -1485,7 +1514,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "num-integer", "num-traits", ] @@ -1496,14 +1525,14 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4547ee5541c18742396ae2c895d0717d0f886d8823b8399cdaf7b07d63ad0480" dependencies = [ - "autocfg 0.1.7", + "autocfg 0.1.8", "byteorder", "lazy_static", "libm", "num-integer", "num-iter", "num-traits", - "rand 0.8.4", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -1514,7 +1543,7 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "num-traits", ] @@ -1524,7 +1553,7 @@ version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "num-integer", "num-traits", ] @@ -1535,7 +1564,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "libm", ] @@ -1568,7 +1597,7 @@ dependencies = [ "chrono", "getrandom 0.2.4", "http", - "rand 0.8.4", + "rand 0.8.5", "reqwest", "serde", "serde_json", @@ -1622,7 +1651,7 @@ version = "0.9.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" dependencies = [ - "autocfg 1.0.1", + "autocfg 1.1.0", "cc", "libc", "pkg-config", @@ -1637,7 +1666,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.5", +] + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.1", ] [[package]] @@ -1654,6 +1693,19 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "parking_lot_core" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28141e0cc4143da2443301914478dc976a61ffdb3f043058310c70df2fed8954" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + [[package]] name = "parse-zoneinfo" version = "0.3.0" @@ -1663,6 +1715,12 @@ dependencies = [ "regex", ] +[[package]] +name = "paste" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0744126afe1a6dd7f394cb50a716dbe086cb06e255e53d8d0185d82828358fb5" + [[package]] name = "pear" version = "0.2.3" @@ -1770,7 +1828,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ "phf_shared", - "rand 0.8.4", + "rand 0.8.5", ] [[package]] @@ -1825,6 +1883,32 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +[[package]] +name = "poise" +version = "0.1.0" +source = "git+https://github.com/kangalioo/poise?branch=master#38bcca284cbc9fb52cd770d7af64fbd4b3495cc8" +dependencies = [ + "async-trait", + "futures-core", + "futures-util", + "once_cell", + "poise_macros", + "regex", + "serenity", + "tokio", +] + +[[package]] +name = "poise_macros" +version = "0.1.0" +source = "git+https://github.com/kangalioo/poise?branch=master#38bcca284cbc9fb52cd770d7af64fbd4b3495cc8" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "polyval" version = "0.5.3" @@ -1900,19 +1984,18 @@ dependencies = [ "libc", "rand_chacha 0.2.2", "rand_core 0.5.1", - "rand_hc 0.2.0", + "rand_hc", ] [[package]] name = "rand" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", "rand_core 0.6.3", - "rand_hc 0.3.1", ] [[package]] @@ -1962,15 +2045,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core 0.6.3", -] - [[package]] name = "redox_syscall" version = "0.2.10" @@ -2045,11 +2119,11 @@ dependencies = [ "chrono-tz 0.5.3", "dotenv", "env_logger", - "humantime", "lazy_static", "levenshtein", "log", "num-integer", + "poise", "postman", "rand 0.7.3", "regex", @@ -2060,7 +2134,6 @@ dependencies = [ "serde", "serde_json", "serde_repr", - "serenity", "sqlx", "tokio", ] @@ -2116,7 +2189,7 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", - "rustls 0.20.2", + "rustls 0.20.4", "rustls-pemfile", "serde", "serde_json", @@ -2124,7 +2197,7 @@ dependencies = [ "tokio", "tokio-native-tls", "tokio-rustls 0.23.2", - "tokio-util", + "tokio-util 0.6.9", "url", "wasm-bindgen", "wasm-bindgen-futures", @@ -2172,7 +2245,7 @@ dependencies = [ [[package]] name = "rocket" version = "0.5.0-rc.1" -source = "git+https://github.com/SergioBenitez/Rocket?branch=master#8cae077ba1d54b92cdef3e171a730b819d5eeb8e" +source = "git+https://github.com/SergioBenitez/Rocket?branch=master#66d18bf66517e2765494d082629e9b9748ff8ad6" dependencies = [ "async-stream", "async-trait", @@ -2188,9 +2261,9 @@ dependencies = [ "memchr", "multer", "num_cpus", - "parking_lot", + "parking_lot 0.12.0", "pin-project-lite", - "rand 0.8.4", + "rand 0.8.5", "ref-cast", "rocket_codegen", "rocket_http", @@ -2201,7 +2274,7 @@ dependencies = [ "time 0.3.7", "tokio", "tokio-stream", - "tokio-util", + "tokio-util 0.7.0", "ubyte", "version_check", "yansi", @@ -2210,7 +2283,7 @@ dependencies = [ [[package]] name = "rocket_codegen" version = "0.5.0-rc.1" -source = "git+https://github.com/SergioBenitez/Rocket?branch=master#8cae077ba1d54b92cdef3e171a730b819d5eeb8e" +source = "git+https://github.com/SergioBenitez/Rocket?branch=master#66d18bf66517e2765494d082629e9b9748ff8ad6" dependencies = [ "devise", "glob", @@ -2225,7 +2298,7 @@ dependencies = [ [[package]] name = "rocket_dyn_templates" version = "0.1.0-rc.1" -source = "git+https://github.com/SergioBenitez/Rocket?branch=master#8cae077ba1d54b92cdef3e171a730b819d5eeb8e" +source = "git+https://github.com/SergioBenitez/Rocket?branch=master#66d18bf66517e2765494d082629e9b9748ff8ad6" dependencies = [ "glob", "normpath", @@ -2237,7 +2310,7 @@ dependencies = [ [[package]] name = "rocket_http" version = "0.5.0-rc.1" -source = "git+https://github.com/SergioBenitez/Rocket?branch=master#8cae077ba1d54b92cdef3e171a730b819d5eeb8e" +source = "git+https://github.com/SergioBenitez/Rocket?branch=master#66d18bf66517e2765494d082629e9b9748ff8ad6" dependencies = [ "cookie", "either", @@ -2276,7 +2349,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand 0.8.4", + "rand 0.8.5", "subtle", "zeroize", ] @@ -2296,9 +2369,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.2" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" +checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921" dependencies = [ "log", "ring", @@ -2380,9 +2453,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fed7948b6c68acbb6e20c334f55ad635dc0f75506963de4464289fbd3b051ac" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" dependencies = [ "bitflags", "core-foundation", @@ -2393,9 +2466,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a57321bf8bc2362081b2599912d2961fe899c0efadf1b4b2f8d48b3e253bb96c" +checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" dependencies = [ "core-foundation-sys", "libc", @@ -2423,11 +2496,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ - "itoa 1.0.1", + "itoa", "ryu", "serde", ] @@ -2459,7 +2532,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.1", + "itoa", "ryu", "serde", ] @@ -2467,7 +2540,7 @@ dependencies = [ [[package]] name = "serenity" version = "0.10.10" -source = "git+https://github.com/serenity-rs/serenity?branch=next#9bf5f25ab8421a0513a199930a7a624da3d3c853" +source = "git+https://github.com/serenity-rs/serenity?branch=next#85b3d8c665d9c15f47aeb7afeb738c58a32a5e64" dependencies = [ "async-trait", "async-tungstenite", @@ -2480,7 +2553,7 @@ dependencies = [ "futures", "mime", "mime_guess", - "parking_lot", + "parking_lot 0.11.2", "percent-encoding", "reqwest", "serde", @@ -2531,13 +2604,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99c3bd8169c58782adad9290a9af5939994036b76187f7b4f0e6de91dbbfc0ec" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.2", + "digest 0.10.3", ] [[package]] @@ -2629,9 +2702,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692749de69603d81e016212199d73a2e14ee20e2def7d7914919e8db5d4d48b9" +checksum = "fc15591eb44ffb5816a4a70a7efd5dd87bfd3aa84c4c200401c4396140525826" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2639,9 +2712,9 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518be6f6fff5ca76f985d434f9c37f3662af279642acf730388f271dff7b9016" +checksum = "195183bf6ff8328bb82c0511a83faf60aacf75840103388851db61d7a9854ae3" dependencies = [ "ahash", "atoi", @@ -2651,9 +2724,7 @@ dependencies = [ "bytes", "chrono", "crc", - "crossbeam-channel", "crossbeam-queue", - "crossbeam-utils", "digest 0.9.0", "either", "futures-channel", @@ -2664,15 +2735,15 @@ dependencies = [ "hashlink", "hex", "indexmap", - "itoa 1.0.1", + "itoa", "libc", "log", "memchr", "num-bigint", "once_cell", - "parking_lot", + "paste", "percent-encoding", - "rand 0.8.4", + "rand 0.8.5", "rsa", "rustls 0.19.1", "sha-1 0.9.8", @@ -2690,9 +2761,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e45140529cf1f90a5e1c2e561500ca345821a1c513652c8f486bbf07407cc8" +checksum = "eee35713129561f5e55c554bba1c378e2a7e67f81257b7311183de98c50e6f94" dependencies = [ "dotenv", "either", @@ -2709,9 +2780,9 @@ dependencies = [ [[package]] name = "sqlx-rt" -version = "0.5.10" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8061cbaa91ee75041514f67a09398c65a64efed72c90151ecd47593bad53da99" +checksum = "b555e70fbbf84e269ec3858b7a6515bcfe7a166a7cc9c636dd6efd20431678b6" dependencies = [ "once_cell", "tokio", @@ -2746,6 +2817,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "subtle" version = "2.4.1" @@ -2803,7 +2880,7 @@ dependencies = [ "percent-encoding", "pest", "pest_derive", - "rand 0.8.4", + "rand 0.8.5", "regex", "serde", "serde_json", @@ -2865,7 +2942,7 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" dependencies = [ - "itoa 1.0.1", + "itoa", "libc", "num_threads", "time-macros", @@ -2894,19 +2971,20 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.16.1" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" +checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" dependencies = [ "bytes", "libc", "memchr", - "mio 0.7.14", + "mio 0.8.0", "num_cpus", "once_cell", - "parking_lot", + "parking_lot 0.12.0", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", "winapi 0.3.9", ] @@ -2949,7 +3027,7 @@ version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" dependencies = [ - "rustls 0.20.2", + "rustls 0.20.4", "tokio", "webpki 0.22.0", ] @@ -2979,6 +3057,20 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64910e1b9c1901aaf5375561e35b9c057d95ff41a44ede043a03e09279eabaf1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.5.8" @@ -2996,9 +3088,9 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.29" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105" +checksum = "f6c650a8ef0cd2dd93736f033d21cbd1224c5a967aa0c258d00fcf7dafef9b9f" dependencies = [ "cfg-if 1.0.0", "log", @@ -3009,9 +3101,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +checksum = "8276d9a4a3a558d7b7ad5303ad50b53d58264641b82914b7ada36bd762e7a716" dependencies = [ "proc-macro2", "quote", @@ -3020,11 +3112,12 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4" +checksum = "03cfcb51380632a72d3111cb8d3447a8d908e577d31beeac006f836383d29a23" dependencies = [ "lazy_static", + "valuable", ] [[package]] @@ -3040,9 +3133,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5312f325fe3588e277415f5a6cca1f4ccad0f248c4cd5a4bd33032d7286abc22" +checksum = "9e0ab7bdc962035a87fba73f3acca9b8a8d0034c2e6f60b84aeaaddddc155dce" dependencies = [ "ansi_term", "lazy_static", @@ -3074,8 +3167,8 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.4", - "rustls 0.20.2", + "rand 0.8.5", + "rustls 0.20.4", "sha-1 0.9.8", "thiserror", "url", @@ -3196,9 +3289,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-xid" @@ -3256,6 +3349,12 @@ dependencies = [ "getrandom 0.2.4", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -3458,6 +3557,49 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df6e476185f92a12c072be4a189a0210dcdcf512a1891d6dff9edb874deadc6" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8e92753b1c443191654ec532f14c199742964a061be25d77d7a96f09db20bf5" + +[[package]] +name = "windows_i686_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a711c68811799e017b6038e0922cb27a5e2f43a2ddb609fe0b6f3eeda9de615" + +[[package]] +name = "windows_i686_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c11bb1a02615db74680b32a68e2d61f553cc24c4eb5b4ca10311740e44172" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c912b12f7454c6620635bbff3450962753834be2a594819bd5e945af18ec64bc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "504a2476202769977a040c6364301a3f65d0cc9e3fb08600b2bda150a0488316" + [[package]] name = "winreg" version = "0.7.0" @@ -3494,9 +3636,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e8f13fef10b63c06356d65d416b070798ddabcadc10d3ece0c5be9b3c7eddb" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 4ade220..23f13b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,8 @@ authors = ["jellywx "] edition = "2018" [dependencies] +poise = { git = "https://github.com/kangalioo/poise", branch = "master" } dotenv = "0.15" -humantime = "2.1" tokio = { version = "1", features = ["process", "full"] } reqwest = "0.11" regex = "1.4" @@ -33,20 +33,3 @@ path = "postman" [dependencies.reminder_web] path = "web" - -[dependencies.serenity] -git = "https://github.com/serenity-rs/serenity" -branch = "next" -default-features = false -features = [ - "builder", - "client", - "cache", - "gateway", - "http", - "model", - "utils", - "rustls_backend", - "collector", - "unstable_discord_api" -] diff --git a/src/commands/info_cmds.rs b/src/commands/info_cmds.rs index f6232f5..3376f64 100644 --- a/src/commands/info_cmds.rs +++ b/src/commands/info_cmds.rs @@ -1,16 +1,11 @@ use chrono::offset::Utc; -use regex_command_attr::command; -use serenity::{builder::CreateEmbedFooter, client::Context}; +use poise::serenity::builder::CreateEmbedFooter; -use crate::{ - framework::{CommandInvoke, CreateGenericResponse}, - models::CtxData, - THEME_COLOR, -}; +use crate::{models::CtxData, Context, Error, THEME_COLOR}; -fn footer(ctx: &Context) -> impl FnOnce(&mut CreateEmbedFooter) -> &mut CreateEmbedFooter { - let shard_count = ctx.cache.shard_count(); - let shard = ctx.shard_id; +fn footer(ctx: Context<'_>) -> impl FnOnce(&mut CreateEmbedFooter) -> &mut CreateEmbedFooter { + let shard_count = ctx.discord().cache.shard_count(); + let shard = ctx.discord().shard_id; move |f| { f.text(format!( @@ -22,15 +17,14 @@ fn footer(ctx: &Context) -> impl FnOnce(&mut CreateEmbedFooter) -> &mut CreateEm } } -#[command] -#[description("Get an overview of the bot commands")] -async fn help(ctx: &Context, invoke: &mut CommandInvoke) { +/// Get an overview of bot commands +#[poise::command(slash_command)] +pub async fn help(ctx: Context<'_>) -> Result<(), Error> { let footer = footer(ctx); - let _ = invoke - .respond( - &ctx, - CreateGenericResponse::new().embed(|e| { + let _ = ctx + .send(|m| { + m.embed(|e| { e.title("Help") .color(*THEME_COLOR) .description( @@ -60,21 +54,21 @@ __Advanced Commands__ ", ) .footer(footer) - }), - ) + }) + }) .await; + + Ok(()) } -#[command] -#[aliases("invite")] -#[description("Get information about the bot")] -async fn info(ctx: &Context, invoke: &mut CommandInvoke) { +/// Get information about the bot +#[poise::command(slash_command)] +pub async fn info(ctx: Context<'_>) -> Result<(), Error> { let footer = footer(ctx); - let _ = invoke - .respond( - ctx.http.clone(), - CreateGenericResponse::new().embed(|e| { + let _ = ctx + .send(|m| { + m.embed(|e| { e.title("Info") .description(format!( "Help: `/help` @@ -89,21 +83,19 @@ Use our dashboard: https://reminder-bot.com/", )) .footer(footer) .color(*THEME_COLOR) - }), - ) + }) + }) .await; + + Ok(()) } -#[command] -#[description("Details on supporting the bot and Patreon benefits")] -#[group("Info")] -async fn donate(ctx: &Context, invoke: &mut CommandInvoke) { +/// Details on supporting the bot and Patreon benefits +#[poise::command(slash_command)] +pub async fn donate(ctx: Context<'_>) -> Result<(), Error> { let footer = footer(ctx); - let _ = invoke - .respond( - ctx.http.clone(), - CreateGenericResponse::new().embed(|e| { + let _ = ctx.send(|m| m.embed(|e| { e.title("Donate") .description("Thinking of adding a monthly contribution? Click below for my Patreon and official bot server :) @@ -125,38 +117,41 @@ Just $2 USD/month! }), ) .await; + + Ok(()) } -#[command] -#[description("Get the link to the online dashboard")] -#[group("Info")] -async fn dashboard(ctx: &Context, invoke: &mut CommandInvoke) { +/// Get the link to the online dashboard +#[poise::command(slash_command)] +pub async fn dashboard(ctx: Context<'_>) -> Result<(), Error> { let footer = footer(ctx); - let _ = invoke - .respond( - ctx.http.clone(), - CreateGenericResponse::new().embed(|e| { + let _ = ctx + .send(|m| { + m.embed(|e| { e.title("Dashboard") .description("**https://reminder-bot.com/dashboard**") .footer(footer) .color(*THEME_COLOR) - }), - ) + }) + }) .await; + + Ok(()) } -#[command] -#[description("View the current time in your selected timezone")] -#[group("Info")] -async fn clock(ctx: &Context, invoke: &mut CommandInvoke) { - let ud = ctx.user_data(&invoke.author_id()).await.unwrap(); - let now = Utc::now().with_timezone(&ud.timezone()); +/// View the current time in a user's selected timezone +#[poise::command(slash_command)] +pub async fn clock(ctx: Context<'_>) -> Result<(), Error> { + ctx.defer_ephemeral().await?; - let _ = invoke - .respond( - ctx.http.clone(), - CreateGenericResponse::new().content(format!("Current time: {}", now.format("%H:%M"))), - ) - .await; + let tz = ctx.timezone().await; + let now = Utc::now().with_timezone(&tz); + + ctx.send(|m| { + m.ephemeral(true).content(format!("Time in **{}**: `{}`", tz, now.format("%H:%M"))) + }) + .await?; + + Ok(()) } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index e53c6f3..8ad997e 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,4 +1,4 @@ pub mod info_cmds; pub mod moderation_cmds; -pub mod reminder_cmds; -pub mod todo_cmds; +//pub mod reminder_cmds; +//pub mod todo_cmds; diff --git a/src/commands/moderation_cmds.rs b/src/commands/moderation_cmds.rs index cada0e4..b554cc4 100644 --- a/src/commands/moderation_cmds.rs +++ b/src/commands/moderation_cmds.rs @@ -1,54 +1,60 @@ use chrono::offset::Utc; use chrono_tz::{Tz, TZ_VARIANTS}; use levenshtein::levenshtein; -use regex_command_attr::command; -use serenity::client::Context; +use poise::CreateReply; use crate::{ - component_models::pager::{MacroPager, Pager}, consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR}, - framework::{CommandInvoke, CommandOptions, CreateGenericResponse, OptionValue}, - hooks::{CHECK_GUILD_PERMISSIONS_HOOK, GUILD_ONLY_HOOK}, + hooks::guild_only, models::{command_macro::CommandMacro, CtxData}, - PopularTimezones, RecordingMacros, RegexFramework, SQLPool, + Context, Data, Error, }; -#[command("timezone")] -#[description("Select your timezone")] -#[arg( - name = "timezone", - description = "Timezone to use from this list: https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee", - kind = "String", - required = false -)] -async fn timezone(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOptions) { - let pool = ctx.data.read().await.get::().cloned().unwrap(); - let mut user_data = ctx.user_data(invoke.author_id()).await.unwrap(); +async fn timezone_autocomplete(ctx: Context<'_>, partial: String) -> Vec { + if partial.is_empty() { + ctx.data().popular_timezones.iter().map(|t| t.to_string()).collect::>() + } else { + TZ_VARIANTS + .iter() + .filter(|tz| tz.to_string().contains(&partial)) + .take(25) + .map(|t| t.to_string()) + .collect::>() + } +} + +/// Select your timezone +#[poise::command(slash_command)] +pub async fn timezone( + ctx: Context<'_>, + #[description = "Timezone to use from this list: https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee"] + #[autocomplete = "timezone_autocomplete"] + timezone: Option, +) -> Result<(), Error> { + let mut user_data = ctx.author_data().await.unwrap(); let footer_text = format!("Current timezone: {}", user_data.timezone); - if let Some(OptionValue::String(timezone)) = args.get("timezone") { + if let Some(timezone) = timezone { match timezone.parse::() { Ok(tz) => { user_data.timezone = timezone.clone(); - user_data.commit_changes(&pool).await; + user_data.commit_changes(&ctx.data().database).await; let now = Utc::now().with_timezone(&tz); - let _ = invoke - .respond( - ctx.http.clone(), - CreateGenericResponse::new().embed(|e| { - e.title("Timezone Set") - .description(format!( - "Timezone has been set to **{}**. Your current time should be `{}`", - timezone, - now.format("%H:%M").to_string() - )) - .color(*THEME_COLOR) - }), - ) - .await; + ctx.send(|m| { + m.embed(|e| { + e.title("Timezone Set") + .description(format!( + "Timezone has been set to **{}**. Your current time should be `{}`", + timezone, + now.format("%H:%M").to_string() + )) + .color(*THEME_COLOR) + }) + }) + .await?; } Err(_) => { @@ -56,8 +62,8 @@ async fn timezone(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOption .iter() .filter(|tz| { timezone.contains(&tz.to_string()) - || tz.to_string().contains(timezone) - || levenshtein(&tz.to_string(), timezone) < 4 + || tz.to_string().contains(&timezone) + || levenshtein(&tz.to_string(), &timezone) < 4 }) .take(25) .map(|t| t.to_owned()) @@ -74,25 +80,21 @@ async fn timezone(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOption ) }); - let _ = invoke - .respond( - ctx.http.clone(), - CreateGenericResponse::new().embed(|e| { - e.title("Timezone Not Recognized") - .description("Possibly you meant one of the following timezones, otherwise click [here](https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee):") - .color(*THEME_COLOR) - .fields(fields) - .footer(|f| f.text(footer_text)) - .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee") - }), - ) - .await; + ctx.send(|m| { + m.embed(|e| { + e.title("Timezone Not Recognized") + .description("Possibly you meant one of the following timezones, otherwise click [here](https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee):") + .color(*THEME_COLOR) + .fields(fields) + .footer(|f| f.text(footer_text)) + .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee") + }) + }) + .await?; } } } else { - let popular_timezones = ctx.data.read().await.get::().cloned().unwrap(); - - let popular_timezones_iter = popular_timezones.iter().map(|t| { + let popular_timezones_iter = ctx.data().popular_timezones.iter().map(|t| { ( t.to_string(), format!("🕗 `{}`", Utc::now().with_timezone(t).format("%H:%M").to_string()), @@ -100,279 +102,278 @@ async fn timezone(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOption ) }); - let _ = invoke - .respond( - ctx.http.clone(), - CreateGenericResponse::new().embed(|e| { - e.title("Timezone Usage") - .description( - "**Usage:** + ctx.send(|m| { + m.embed(|e| { + e.title("Timezone Usage") + .description( + "**Usage:** `/timezone Name` **Example:** `/timezone Europe/London` You may want to use one of the popular timezones below, otherwise click [here](https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee):", + ) + .color(*THEME_COLOR) + .fields(popular_timezones_iter) + .footer(|f| f.text(footer_text)) + .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee") + }) + }) + .await?; + } + + Ok(()) +} + +async fn macro_name_autocomplete(ctx: Context<'_>, partial: String) -> Vec { + sqlx::query!( + " +SELECT name +FROM macro +WHERE + guild_id = (SELECT id FROM guilds WHERE guild = ?) + AND name LIKE CONCAT(?, '%')", + ctx.guild_id().unwrap().0, + partial, + ) + .fetch_all(&ctx.data().database) + .await + .unwrap_or(vec![]) + .iter() + .map(|s| s.name.clone()) + .collect() +} + +/// Record and replay command sequences +#[poise::command(slash_command, rename = "macro", check = "guild_only")] +pub async fn macro_base(_ctx: Context<'_>) -> Result<(), Error> { + Ok(()) +} + +/// Start recording up to 5 commands to replay +#[poise::command(slash_command, rename = "record", check = "guild_only")] +pub async fn record_macro( + ctx: Context<'_>, + #[description = "Name for the new macro"] name: String, + #[description = "Description for the new macro"] description: Option, +) -> Result<(), Error> { + let guild_id = ctx.guild_id().unwrap(); + + let row = sqlx::query!( + " +SELECT 1 as _e FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?", + guild_id.0, + name + ) + .fetch_one(&ctx.data().database) + .await; + + if row.is_ok() { + ctx.send(|m| { + m.ephemeral(true).embed(|e| { + e.title("Unique Name Required") + .description( + "A macro already exists under this name. +Please select a unique name for your macro.", + ) + .color(*THEME_COLOR) + }) + }) + .await?; + } else { + let okay = { + let mut lock = ctx.data().recording_macros.write().await; + + if lock.contains_key(&(guild_id, ctx.author().id)) { + false + } else { + lock.insert( + (guild_id, ctx.author().id), + CommandMacro { guild_id, name, description, commands: vec![] }, + ); + true + } + }; + + if okay { + ctx.send(|m| { + m.ephemeral(true).embed(|e| { + e.title("Macro Recording Started") + .description( + "Run up to 5 commands, or type `/macro finish` to stop at any point. +Any commands ran as part of recording will be inconsequential", ) .color(*THEME_COLOR) - .fields(popular_timezones_iter) - .footer(|f| f.text(footer_text)) - .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee") - }), - ) - .await; - } -} - -#[command("macro")] -#[description("Record and replay command sequences")] -#[subcommand("record")] -#[description("Start recording up to 5 commands to replay")] -#[arg(name = "name", description = "Name for the new macro", kind = "String", required = true)] -#[arg( - name = "description", - description = "Description for the new macro", - kind = "String", - required = false -)] -#[subcommand("finish")] -#[description("Finish current recording")] -#[subcommand("list")] -#[description("List recorded macros")] -#[subcommand("run")] -#[description("Run a recorded macro")] -#[arg(name = "name", description = "Name of the macro to run", kind = "String", required = true)] -#[subcommand("delete")] -#[description("Delete a recorded macro")] -#[arg(name = "name", description = "Name of the macro to delete", kind = "String", required = true)] -#[supports_dm(false)] -#[hook(GUILD_ONLY_HOOK)] -#[hook(CHECK_GUILD_PERMISSIONS_HOOK)] -async fn macro_cmd(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOptions) { - let pool = ctx.data.read().await.get::().cloned().unwrap(); - - match args.subcommand.clone().unwrap().as_str() { - "record" => { - let guild_id = invoke.guild_id().unwrap(); - - let name = args.get("name").unwrap().to_string(); - - let row = sqlx::query!( - "SELECT 1 as _e FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?", - guild_id.0, - name - ) - .fetch_one(&pool) - .await; - - if row.is_ok() { - let _ = invoke - .respond( - &ctx, - CreateGenericResponse::new().ephemeral().embed(|e| { - e - .title("Unique Name Required") - .description("A macro already exists under this name. Please select a unique name for your macro.") - .color(*THEME_COLOR) - }), - ) - .await; - } else { - let macro_buffer = ctx.data.read().await.get::().cloned().unwrap(); - - let okay = { - let mut lock = macro_buffer.write().await; - - if lock.contains_key(&(guild_id, invoke.author_id())) { - false - } else { - lock.insert( - (guild_id, invoke.author_id()), - CommandMacro { - guild_id, - name, - description: args.get("description").map(|d| d.to_string()), - commands: vec![], - }, - ); - true - } - }; - - if okay { - let _ = invoke - .respond( - &ctx, - CreateGenericResponse::new().ephemeral().embed(|e| { - e - .title("Macro Recording Started") - .description( -"Run up to 5 commands, or type `/macro finish` to stop at any point. -Any commands ran as part of recording will be inconsequential") - .color(*THEME_COLOR) - }), - ) - .await; - } else { - let _ = invoke - .respond( - &ctx, - CreateGenericResponse::new().ephemeral().embed(|e| { - e.title("Macro Already Recording") - .description( - "You are already recording a macro in this server. + }) + }) + .await?; + } else { + ctx.send(|m| { + m.ephemeral(true).embed(|e| { + e.title("Macro Already Recording") + .description( + "You are already recording a macro in this server. Please use `/macro finish` to end this recording before starting another.", - ) - .color(*THEME_COLOR) - }), ) - .await; - } - } + .color(*THEME_COLOR) + }) + }) + .await?; } - "finish" => { - let key = (invoke.guild_id().unwrap(), invoke.author_id()); - let macro_buffer = ctx.data.read().await.get::().cloned().unwrap(); - - { - let lock = macro_buffer.read().await; - let contained = lock.get(&key); - - if contained.map_or(true, |cmacro| cmacro.commands.is_empty()) { - let _ = invoke - .respond( - &ctx, - CreateGenericResponse::new().embed(|e| { - e.title("No Macro Recorded") - .description("Use `/macro record` to start recording a macro") - .color(*THEME_COLOR) - }), - ) - .await; - } else { - let command_macro = contained.unwrap(); - let json = serde_json::to_string(&command_macro.commands).unwrap(); - - sqlx::query!( - "INSERT INTO macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)", - command_macro.guild_id.0, - command_macro.name, - command_macro.description, - json - ) - .execute(&pool) - .await - .unwrap(); - - let _ = invoke - .respond( - &ctx, - CreateGenericResponse::new().embed(|e| { - e.title("Macro Recorded") - .description("Use `/macro run` to execute the macro") - .color(*THEME_COLOR) - }), - ) - .await; - } - } - - { - let mut lock = macro_buffer.write().await; - lock.remove(&key); - } - } - "list" => { - let macros = CommandMacro::from_guild(ctx, invoke.guild_id().unwrap()).await; - - let resp = show_macro_page(¯os, 0); - - invoke.respond(&ctx, resp).await.unwrap(); - } - "run" => { - let macro_name = args.get("name").unwrap().to_string(); - - match sqlx::query!( - "SELECT commands FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?", - invoke.guild_id().unwrap().0, - macro_name - ) - .fetch_one(&pool) - .await - { - Ok(row) => { - invoke.defer(&ctx).await; - - let commands: Vec = - serde_json::from_str(&row.commands).unwrap(); - let framework = ctx.data.read().await.get::().cloned().unwrap(); - - for command in commands { - framework.run_command_from_options(ctx, invoke, command).await; - } - } - - Err(sqlx::Error::RowNotFound) => { - let _ = invoke - .respond( - &ctx, - CreateGenericResponse::new() - .content(format!("Macro \"{}\" not found", macro_name)), - ) - .await; - } - - Err(e) => { - panic!("{}", e); - } - } - } - "delete" => { - let macro_name = args.get("name").unwrap().to_string(); - - match sqlx::query!( - "SELECT id FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?", - invoke.guild_id().unwrap().0, - macro_name - ) - .fetch_one(&pool) - .await - { - Ok(row) => { - sqlx::query!("DELETE FROM macro WHERE id = ?", row.id) - .execute(&pool) - .await - .unwrap(); - - let _ = invoke - .respond( - &ctx, - CreateGenericResponse::new() - .content(format!("Macro \"{}\" deleted", macro_name)), - ) - .await; - } - - Err(sqlx::Error::RowNotFound) => { - let _ = invoke - .respond( - &ctx, - CreateGenericResponse::new() - .content(format!("Macro \"{}\" not found", macro_name)), - ) - .await; - } - - Err(e) => { - panic!("{}", e); - } - } - } - _ => {} } + + Ok(()) } -pub fn max_macro_page(macros: &[CommandMacro]) -> usize { +/// Finish current macro recording +#[poise::command( + slash_command, + rename = "finish", + check = "guild_only", + identifying_name = "macro_finish" +)] +pub async fn finish_macro(ctx: Context<'_>) -> Result<(), Error> { + let key = (ctx.guild_id().unwrap(), ctx.author().id); + + { + let lock = ctx.data().recording_macros.read().await; + let contained = lock.get(&key); + + if contained.map_or(true, |cmacro| cmacro.commands.is_empty()) { + ctx.send(|m| { + m.embed(|e| { + e.title("No Macro Recorded") + .description("Use `/macro record` to start recording a macro") + .color(*THEME_COLOR) + }) + }) + .await?; + } else { + let command_macro = contained.unwrap(); + let json = serde_json::to_string(&command_macro.commands).unwrap(); + + sqlx::query!( + "INSERT INTO macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)", + command_macro.guild_id.0, + command_macro.name, + command_macro.description, + json + ) + .execute(&ctx.data().database) + .await + .unwrap(); + + ctx.send(|m| { + m.embed(|e| { + e.title("Macro Recorded") + .description("Use `/macro run` to execute the macro") + .color(*THEME_COLOR) + }) + }) + .await?; + } + } + + { + let mut lock = ctx.data().recording_macros.write().await; + lock.remove(&key); + } + + Ok(()) +} + +/// List recorded macros +#[poise::command(slash_command, rename = "list", check = "guild_only")] +pub async fn list_macro(ctx: Context<'_>) -> Result<(), Error> { + // let macros = CommandMacro::from_guild(&ctx.data().database, ctx.guild_id().unwrap()).await; + let macros: Vec> = vec![]; + + let resp = show_macro_page(¯os, 0); + + ctx.send(|m| { + *m = resp; + m + }) + .await?; + + Ok(()) +} + +/// Run a recorded macro +#[poise::command(slash_command, rename = "run", check = "guild_only")] +pub async fn run_macro( + ctx: Context<'_>, + #[description = "Name of macro to run"] + #[autocomplete = "macro_name_autocomplete"] + name: String, +) -> Result<(), Error> { + match sqlx::query!( + " +SELECT commands FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?", + ctx.guild_id().unwrap().0, + name + ) + .fetch_one(&ctx.data().database) + .await + { + Ok(row) => { + ctx.defer().await?; + + // TODO TODO TODO!!!!!!!! RUN COMMAND FROM MACRO + } + + Err(sqlx::Error::RowNotFound) => { + ctx.say(format!("Macro \"{}\" not found", name)).await?; + } + + Err(e) => { + panic!("{}", e); + } + } + + Ok(()) +} + +/// Delete a recorded macro +#[poise::command(slash_command, rename = "delete", check = "guild_only")] +pub async fn delete_macro( + ctx: Context<'_>, + #[description = "Name of macro to delete"] + #[autocomplete = "macro_name_autocomplete"] + name: String, +) -> Result<(), Error> { + match sqlx::query!( + " +SELECT id FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?", + ctx.guild_id().unwrap().0, + name + ) + .fetch_one(&ctx.data().database) + .await + { + Ok(row) => { + sqlx::query!("DELETE FROM macro WHERE id = ?", row.id) + .execute(&ctx.data().database) + .await + .unwrap(); + + ctx.say(format!("Macro \"{}\" deleted", name)).await?; + } + + Err(sqlx::Error::RowNotFound) => { + ctx.say(format!("Macro \"{}\" not found", name)).await?; + } + + Err(e) => { + panic!("{}", e); + } + } + + Ok(()) +} + +pub fn max_macro_page(macros: &[CommandMacro]) -> usize { let mut skipped_char_count = 0; macros @@ -396,15 +397,30 @@ pub fn max_macro_page(macros: &[CommandMacro]) -> usize { }) } -pub fn show_macro_page(macros: &[CommandMacro], page: usize) -> CreateGenericResponse { +pub fn show_macro_page(macros: &[CommandMacro], page: usize) -> CreateReply { + let mut reply = CreateReply::default(); + + reply.embed(|e| { + e.title("Macros") + .description("No Macros Set Up. Use `/macro record` to get started.") + .color(*THEME_COLOR) + }); + + reply + + /* let pager = MacroPager::new(page); if macros.is_empty() { - return CreateGenericResponse::new().embed(|e| { + let mut reply = CreateReply::default(); + + reply.embed(|e| { e.title("Macros") .description("No Macros Set Up. Use `/macro record` to get started.") .color(*THEME_COLOR) }); + + return reply; } let pages = max_macro_page(macros); @@ -447,7 +463,9 @@ pub fn show_macro_page(macros: &[CommandMacro], page: usize) -> CreateGenericRes let display = display_vec.join("\n"); - CreateGenericResponse::new() + let mut reply = CreateReply::default(); + + reply .embed(|e| { e.title("Macros") .description(display) @@ -458,5 +476,8 @@ pub fn show_macro_page(macros: &[CommandMacro], page: usize) -> CreateGenericRes pager.create_button_row(pages, comp); comp - }) + }); + + reply + */ } diff --git a/src/consts.rs b/src/consts.rs index 3e6de0e..f1c47ba 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -6,11 +6,12 @@ pub const SELECT_MAX_ENTRIES: usize = 25; pub const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; const THEME_COLOR_FALLBACK: u32 = 0x8fb677; +pub const MACRO_MAX_COMMANDS: usize = 5; use std::{collections::HashSet, env, iter::FromIterator}; +use poise::serenity::model::prelude::AttachmentType; use regex::Regex; -use serenity::model::prelude::AttachmentType; lazy_static! { pub static ref DEFAULT_AVATAR: AttachmentType<'static> = ( diff --git a/src/event_handlers.rs b/src/event_handlers.rs index 3f42bf4..dfcf1c2 100644 --- a/src/event_handlers.rs +++ b/src/event_handlers.rs @@ -1,161 +1,116 @@ use std::{collections::HashMap, env, sync::atomic::Ordering}; use log::{info, warn}; -use serenity::{ - async_trait, - client::{Context, EventHandler}, - model::{ - channel::GuildChannel, - gateway::{Activity, Ready}, - guild::{Guild, UnavailableGuild}, - id::GuildId, - interactions::Interaction, - }, - utils::shard_id, -}; +use poise::serenity::{client::Context, model::interactions::Interaction, utils::shard_id}; -use crate::{ComponentDataModel, Handler, RegexFramework, ReqwestClient, SQLPool}; +use crate::{Data, Error}; -#[async_trait] -impl EventHandler for Handler { - async fn cache_ready(&self, ctx_base: Context, _guilds: Vec) { - info!("Cache Ready!"); - info!("Preparing to send reminders"); +pub async fn listener(ctx: &Context, event: &poise::Event<'_>, data: &Data) -> Result<(), Error> { + match event { + poise::Event::CacheReady { .. } => { + info!("Cache Ready!"); + info!("Preparing to send reminders"); - if !self.is_loop_running.load(Ordering::Relaxed) { - let ctx1 = ctx_base.clone(); - let ctx2 = ctx_base.clone(); + if !data.is_loop_running.load(Ordering::Relaxed) { + let ctx1 = ctx.clone(); + let ctx2 = ctx.clone(); - let pool1 = ctx1.data.read().await.get::().cloned().unwrap(); - let pool2 = ctx2.data.read().await.get::().cloned().unwrap(); + let pool1 = data.database.clone(); + let pool2 = data.database.clone(); - let run_settings = env::var("DONTRUN").unwrap_or_else(|_| "".to_string()); + let run_settings = env::var("DONTRUN").unwrap_or_else(|_| "".to_string()); - if !run_settings.contains("postman") { - tokio::spawn(async move { - postman::initialize(ctx1, &pool1).await; - }); - } else { - warn!("Not running postman") + if !run_settings.contains("postman") { + tokio::spawn(async move { + postman::initialize(ctx1, &pool1).await; + }); + } else { + warn!("Not running postman") + } + + if !run_settings.contains("web") { + tokio::spawn(async move { + reminder_web::initialize(ctx2, pool2).await.unwrap(); + }); + } else { + warn!("Not running web") + } + + data.is_loop_running.swap(true, Ordering::Relaxed); } - - if !run_settings.contains("web") { - tokio::spawn(async move { - reminder_web::initialize(ctx2, pool2).await.unwrap(); - }); - } else { - warn!("Not running web") - } - - self.is_loop_running.swap(true, Ordering::Relaxed); } - } - - async fn channel_delete(&self, ctx: Context, channel: &GuildChannel) { - let pool = ctx - .data - .read() - .await - .get::() - .cloned() - .expect("Could not get SQLPool from data"); - - sqlx::query!( - " + poise::Event::ChannelDelete { channel } => { + sqlx::query!( + " DELETE FROM channels WHERE channel = ? - ", - channel.id.as_u64() - ) - .execute(&pool) - .await - .unwrap(); - } + ", + channel.id.as_u64() + ) + .execute(&data.database) + .await + .unwrap(); + } + poise::Event::GuildCreate { guild, is_new } => { + if *is_new { + let guild_id = guild.id.as_u64().to_owned(); - async fn guild_create(&self, ctx: Context, guild: Guild, is_new: bool) { - if is_new { - let guild_id = guild.id.as_u64().to_owned(); - - { - let pool = ctx.data.read().await.get::().cloned().unwrap(); - - let _ = sqlx::query!("INSERT INTO guilds (guild) VALUES (?)", guild_id) - .execute(&pool) - .await; - } - - if let Ok(token) = env::var("DISCORDBOTS_TOKEN") { - let shard_count = ctx.cache.shard_count(); - let current_shard_id = shard_id(guild_id, shard_count); - - let guild_count = ctx - .cache - .guilds() - .iter() - .filter(|g| shard_id(g.as_u64().to_owned(), shard_count) == current_shard_id) - .count() as u64; - - let mut hm = HashMap::new(); - hm.insert("server_count", guild_count); - hm.insert("shard_id", current_shard_id); - hm.insert("shard_count", shard_count); - - let client = ctx - .data - .read() + sqlx::query!("INSERT INTO guilds (guild) VALUES (?)", guild_id) + .execute(&data.database) .await - .get::() - .cloned() - .expect("Could not get ReqwestClient from data"); + .unwrap(); - let response = client - .post( - format!( - "https://top.gg/api/bots/{}/stats", - ctx.cache.current_user_id().as_u64() + if let Ok(token) = env::var("DISCORDBOTS_TOKEN") { + let shard_count = ctx.cache.shard_count(); + let current_shard_id = shard_id(guild_id, shard_count); + + let guild_count = ctx + .cache + .guilds() + .iter() + .filter(|g| { + shard_id(g.as_u64().to_owned(), shard_count) == current_shard_id + }) + .count() as u64; + + let mut hm = HashMap::new(); + hm.insert("server_count", guild_count); + hm.insert("shard_id", current_shard_id); + hm.insert("shard_count", shard_count); + + let response = data + .http + .post( + format!( + "https://top.gg/api/bots/{}/stats", + ctx.cache.current_user_id().as_u64() + ) + .as_str(), ) - .as_str(), - ) - .header("Authorization", token) - .json(&hm) - .send() - .await; + .header("Authorization", token) + .json(&hm) + .send() + .await; - if let Err(res) = response { - println!("DiscordBots Response: {:?}", res); + if let Err(res) = response { + println!("DiscordBots Response: {:?}", res); + } } } } - } - - async fn guild_delete(&self, ctx: Context, incomplete: UnavailableGuild, _full: Option) { - let pool = ctx.data.read().await.get::().cloned().unwrap(); - let _ = sqlx::query!("DELETE FROM guilds WHERE guild = ?", incomplete.id.0) - .execute(&pool) - .await; - } - - async fn ready(&self, ctx: Context, _: Ready) { - ctx.set_activity(Activity::watching("for /remind")).await; - } - - async fn interaction_create(&self, ctx: Context, interaction: Interaction) { - match interaction { - Interaction::ApplicationCommand(application_command) => { - let framework = ctx - .data - .read() - .await - .get::() - .cloned() - .expect("RegexFramework not found in context"); - - framework.execute(ctx, application_command).await; - } + poise::Event::GuildDelete { incomplete, full } => { + let _ = sqlx::query!("DELETE FROM guilds WHERE guild = ?", incomplete.id.0) + .execute(&data.database) + .await; + } + poise::Event::InteractionCreate { interaction } => match interaction { Interaction::MessageComponent(component) => { - let component_model = ComponentDataModel::from_custom_id(&component.data.custom_id); - component_model.act(&ctx, component).await; + //let component_model = ComponentDataModel::from_custom_id(&component.data.custom_id); + //component_model.act(&ctx, component).await; } _ => {} - } + }, + _ => {} } + + Ok(()) } diff --git a/src/hooks.rs b/src/hooks.rs index fae9bcf..2b5ccd7 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -1,91 +1,74 @@ -use regex_command_attr::check; -use serenity::{client::Context, model::channel::Channel}; +use poise::{serenity::model::channel::Channel, ApplicationCommandOrAutocompleteInteraction}; -use crate::{ - framework::{CommandInvoke, CommandOptions, CreateGenericResponse, HookResult}, - moderation_cmds, RecordingMacros, -}; +use crate::{consts::MACRO_MAX_COMMANDS, Context, Error}; -#[check] -pub async fn guild_only( - ctx: &Context, - invoke: &mut CommandInvoke, - _args: &CommandOptions, -) -> HookResult { - if invoke.guild_id().is_some() { - HookResult::Continue +pub async fn guild_only(ctx: Context<'_>) -> Result { + if ctx.guild_id().is_some() { + Ok(true) } else { - let _ = invoke - .respond( - &ctx, - CreateGenericResponse::new().content("This command can only be used in servers"), - ) - .await; + let _ = ctx.say("This command can only be used in servers").await; - HookResult::Halt + Ok(false) } } -#[check] -pub async fn macro_check( - ctx: &Context, - invoke: &mut CommandInvoke, - args: &CommandOptions, -) -> HookResult { - if let Some(guild_id) = invoke.guild_id() { - if args.command != moderation_cmds::MACRO_CMD_COMMAND.names[0] { - let active_recordings = - ctx.data.read().await.get::().cloned().unwrap(); - let mut lock = active_recordings.write().await; +async fn macro_check(ctx: Context<'_>) -> bool { + if let Context::Application(app_ctx) = ctx { + if let ApplicationCommandOrAutocompleteInteraction::ApplicationCommand(interaction) = + app_ctx.interaction + { + if let Some(guild_id) = ctx.guild_id() { + if ctx.command().identifying_name != "macro_finish" { + let mut lock = ctx.data().recording_macros.write().await; - if let Some(command_macro) = lock.get_mut(&(guild_id, invoke.author_id())) { - if command_macro.commands.len() >= 5 { - let _ = invoke - .respond( - &ctx, - CreateGenericResponse::new().content("5 commands already recorded. Please use `/macro finish` to end recording."), - ) - .await; + if let Some(command_macro) = lock.get_mut(&(guild_id, ctx.author().id)) { + if command_macro.commands.len() >= MACRO_MAX_COMMANDS { + let _ = ctx.send(|m| { + m.ephemeral(true).content( + "5 commands already recorded. Please use `/macro finish` to end recording.", + ) + }) + .await; + } else { + // TODO TODO TODO write command to macro + + let _ = ctx + .send(|m| m.ephemeral(true).content("Command recorded to macro")) + .await; + } + + false + } else { + true + } } else { - command_macro.commands.push(args.clone()); - - let _ = invoke - .respond( - &ctx, - CreateGenericResponse::new().content("Command recorded to macro"), - ) - .await; + true } - - HookResult::Halt } else { - HookResult::Continue + true } } else { - HookResult::Continue + true } } else { - HookResult::Continue + true } } -#[check] -pub async fn check_self_permissions( - ctx: &Context, - invoke: &mut CommandInvoke, - _args: &CommandOptions, -) -> HookResult { - if let Some(guild) = invoke.guild(&ctx) { - let user_id = ctx.cache.current_user_id(); +async fn check_self_permissions(ctx: Context<'_>) -> bool { + if let Some(guild) = ctx.guild() { + let user_id = ctx.discord().cache.current_user_id(); - let manage_webhooks = - guild.member_permissions(&ctx, user_id).await.map_or(false, |p| p.manage_webhooks()); - let (view_channel, send_messages, embed_links) = invoke + let manage_webhooks = guild + .member_permissions(&ctx.discord(), user_id) + .await + .map_or(false, |p| p.manage_webhooks()); + let (view_channel, send_messages, embed_links) = ctx .channel_id() - .to_channel_cached(&ctx) + .to_channel_cached(&ctx.discord()) .map(|c| { if let Channel::Guild(channel) = c { - channel.permissions_for_user(ctx, user_id).ok() + channel.permissions_for_user(&ctx.discord(), user_id).ok() } else { None } @@ -96,12 +79,11 @@ pub async fn check_self_permissions( }); if manage_webhooks && send_messages && embed_links { - HookResult::Continue + true } else { - let _ = invoke - .respond( - &ctx, - CreateGenericResponse::new().content(format!( + let _ = ctx + .send(|m| { + m.content(format!( "Please ensure the bot has the correct permissions: {} **View Channel** @@ -112,41 +94,17 @@ pub async fn check_self_permissions( if send_messages { "✅" } else { "❌" }, if manage_webhooks { "✅" } else { "❌" }, if embed_links { "✅" } else { "❌" }, - )), - ) + )) + }) .await; - HookResult::Halt + false } } else { - HookResult::Continue + true } } -#[check] -pub async fn check_guild_permissions( - ctx: &Context, - invoke: &mut CommandInvoke, - _args: &CommandOptions, -) -> HookResult { - if let Some(guild) = invoke.guild(&ctx) { - let permissions = guild.member_permissions(&ctx, invoke.author_id()).await.unwrap(); - - if !permissions.manage_guild() { - let _ = invoke - .respond( - &ctx, - CreateGenericResponse::new().content( - "You must have the \"Manage Server\" permission to use this command", - ), - ) - .await; - - HookResult::Halt - } else { - HookResult::Continue - } - } else { - HookResult::Continue - } +pub async fn all_checks(ctx: Context<'_>) -> Result { + Ok(macro_check(ctx).await && check_self_permissions(ctx).await) } diff --git a/src/interval_parser.rs b/src/interval_parser.rs index 1d6b933..4ac8ef0 100644 --- a/src/interval_parser.rs +++ b/src/interval_parser.rs @@ -1,5 +1,9 @@ /* -Copyright 2021 Paul Colomiets, 2022 Jude Southworth +With modifications, 2022 Jude Southworth + +Original copyright notice: + +Copyright 2021 Paul Colomiets Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, diff --git a/src/main.rs b/src/main.rs index 1912073..89dc26b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,70 +3,45 @@ extern crate lazy_static; mod commands; -mod component_models; +// mod component_models; mod consts; mod event_handlers; -mod framework; mod hooks; mod interval_parser; mod models; mod time_parser; mod utils; -use std::{ - collections::HashMap, - env, - sync::{atomic::AtomicBool, Arc}, -}; +use std::{collections::HashMap, env, sync::atomic::AtomicBool}; use chrono_tz::Tz; use dotenv::dotenv; -use log::info; -use serenity::{ - client::Client, - http::client::Http, - model::{ - gateway::GatewayIntents, - id::{GuildId, UserId}, - }, - prelude::TypeMapKey, +use poise::serenity::model::{ + gateway::{Activity, GatewayIntents}, + id::{GuildId, UserId}, }; -use sqlx::mysql::MySqlPool; +use sqlx::{MySql, Pool}; use tokio::sync::RwLock; use crate::{ - commands::{info_cmds, moderation_cmds, reminder_cmds, todo_cmds}, - component_models::ComponentDataModel, + commands::{info_cmds, moderation_cmds}, consts::THEME_COLOR, - framework::RegexFramework, + event_handlers::listener, + hooks::all_checks, models::command_macro::CommandMacro, + utils::register_application_commands, }; -struct SQLPool; +type Database = MySql; -impl TypeMapKey for SQLPool { - type Value = MySqlPool; -} +type Error = Box; +type Context<'a> = poise::Context<'a, Data, Error>; -struct ReqwestClient; - -impl TypeMapKey for ReqwestClient { - type Value = Arc; -} - -struct PopularTimezones; - -impl TypeMapKey for PopularTimezones { - type Value = Arc>; -} - -struct RecordingMacros; - -impl TypeMapKey for RecordingMacros { - type Value = Arc>>; -} - -struct Handler { +pub struct Data { + database: Pool, + http: reqwest::Client, + recording_macros: RwLock>>, + popular_timezones: Vec, is_loop_running: AtomicBool, } @@ -76,85 +51,77 @@ async fn main() -> Result<(), Box> { dotenv()?; - let token = env::var("DISCORD_TOKEN").expect("Missing DISCORD_TOKEN from environment"); + let discord_token = env::var("DISCORD_TOKEN").expect("Missing DISCORD_TOKEN from environment"); - let application_id = { - let http = Http::new_with_token(&token); - - http.get_current_application_info().await?.id + let options = poise::FrameworkOptions { + commands: vec![ + info_cmds::help(), + info_cmds::info(), + info_cmds::donate(), + info_cmds::clock(), + info_cmds::dashboard(), + moderation_cmds::timezone(), + poise::Command { + subcommands: vec![ + moderation_cmds::delete_macro(), + moderation_cmds::finish_macro(), + moderation_cmds::list_macro(), + moderation_cmds::record_macro(), + moderation_cmds::run_macro(), + ], + ..moderation_cmds::macro_base() + }, + ], + allowed_mentions: None, + command_check: Some(|ctx| Box::pin(all_checks(ctx))), + listener: |ctx, event, _framework, data| Box::pin(listener(ctx, event, data)), + ..Default::default() }; - let dm_enabled = env::var("DM_ENABLED").map_or(true, |var| var == "1"); + let database = + Pool::connect(&env::var("DATABASE_URL").expect("No database URL provided")).await.unwrap(); - let framework = RegexFramework::new() - .ignore_bots(env::var("IGNORE_BOTS").map_or(true, |var| var == "1")) - .debug_guild(env::var("DEBUG_GUILD").map_or(None, |g| { - Some(GuildId(g.parse::().expect("DEBUG_GUILD must be a guild ID"))) - })) - .dm_enabled(dm_enabled) - // info commands - .add_command(&info_cmds::HELP_COMMAND) - .add_command(&info_cmds::INFO_COMMAND) - .add_command(&info_cmds::DONATE_COMMAND) - .add_command(&info_cmds::DASHBOARD_COMMAND) - .add_command(&info_cmds::CLOCK_COMMAND) - // reminder commands - .add_command(&reminder_cmds::TIMER_COMMAND) - .add_command(&reminder_cmds::REMIND_COMMAND) - // management commands - .add_command(&reminder_cmds::DELETE_COMMAND) - .add_command(&reminder_cmds::LOOK_COMMAND) - .add_command(&reminder_cmds::PAUSE_COMMAND) - .add_command(&reminder_cmds::OFFSET_COMMAND) - .add_command(&reminder_cmds::NUDGE_COMMAND) - // to-do commands - .add_command(&todo_cmds::TODO_COMMAND) - // moderation commands - .add_command(&moderation_cmds::TIMEZONE_COMMAND) - .add_command(&moderation_cmds::MACRO_CMD_COMMAND) - .add_hook(&hooks::CHECK_SELF_PERMISSIONS_HOOK) - .add_hook(&hooks::MACRO_CHECK_HOOK); + let popular_timezones = sqlx::query!( + " +SELECT timezone FROM users GROUP BY timezone ORDER BY COUNT(timezone) DESC LIMIT 21 + " + ) + .fetch_all(&database) + .await + .unwrap() + .iter() + .map(|t| t.timezone.parse::().unwrap()) + .collect::>(); - let framework_arc = Arc::new(framework); + poise::Framework::build() + .token(discord_token) + .user_data_setup(move |ctx, _bot, framework| { + Box::pin(async move { + ctx.set_activity(Activity::watching("for /remind")).await; - let mut client = Client::builder(&token) - .intents(GatewayIntents::GUILDS) - .application_id(application_id.0) - .event_handler(Handler { is_loop_running: AtomicBool::from(false) }) - .await - .expect("Error occurred creating client"); + register_application_commands( + ctx, + framework, + env::var("DEBUG_GUILD") + .map(|inner| GuildId(inner.parse().expect("DEBUG_GUILD not valid"))) + .ok(), + ) + .await + .unwrap(); - { - let pool = MySqlPool::connect( - &env::var("DATABASE_URL").expect("Missing DATABASE_URL from environment"), - ) - .await - .unwrap(); - - let popular_timezones = sqlx::query!( - "SELECT timezone FROM users GROUP BY timezone ORDER BY COUNT(timezone) DESC LIMIT 21" - ) - .fetch_all(&pool) - .await - .unwrap() - .iter() - .map(|t| t.timezone.parse::().unwrap()) - .collect::>(); - - let mut data = client.data.write().await; - - data.insert::(pool); - data.insert::(Arc::new(popular_timezones)); - data.insert::(Arc::new(reqwest::Client::new())); - data.insert::(framework_arc.clone()); - data.insert::(Arc::new(RwLock::new(HashMap::new()))); - } - - framework_arc.build_slash(&client.cache_and_http.http).await; - - info!("Starting client as autosharded"); - - client.start_autosharded().await?; + Ok(Data { + http: reqwest::Client::new(), + database, + popular_timezones, + recording_macros: Default::default(), + is_loop_running: AtomicBool::new(false), + }) + }) + }) + .options(options) + .client_settings(move |client_builder| client_builder.intents(GatewayIntents::GUILDS)) + .run_autosharded() + .await?; Ok(()) } diff --git a/src/models/channel_data.rs b/src/models/channel_data.rs index 114ca9d..a4d7337 100644 --- a/src/models/channel_data.rs +++ b/src/models/channel_data.rs @@ -1,5 +1,5 @@ use chrono::NaiveDateTime; -use serenity::model::channel::Channel; +use poise::serenity::model::channel::Channel; use sqlx::MySqlPool; pub struct ChannelData { diff --git a/src/models/command_macro.rs b/src/models/command_macro.rs index 40be85e..6ad655e 100644 --- a/src/models/command_macro.rs +++ b/src/models/command_macro.rs @@ -1,33 +1,25 @@ -use serenity::{client::Context, model::id::GuildId}; +use poise::serenity::{ + client::Context, + model::{ + id::GuildId, interactions::application_command::ApplicationCommandInteractionDataOption, + }, +}; +use serde::Serialize; -use crate::{framework::CommandOptions, SQLPool}; +#[derive(Serialize)] +pub struct RecordedCommand { + #[serde(skip)] + action: for<'a> fn( + poise::ApplicationContext<'a, U, E>, + &'a [ApplicationCommandInteractionDataOption], + ) -> poise::BoxFuture<'a, Result<(), poise::FrameworkError<'a, U, E>>>, + command_name: String, + options: Vec, +} -pub struct CommandMacro { +pub struct CommandMacro { pub guild_id: GuildId, pub name: String, pub description: Option, - pub commands: Vec, -} - -impl CommandMacro { - pub async fn from_guild(ctx: &Context, guild_id: impl Into) -> Vec { - let pool = ctx.data.read().await.get::().cloned().unwrap(); - let guild_id = guild_id.into(); - - sqlx::query!( - "SELECT * FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)", - guild_id.0 - ) - .fetch_all(&pool) - .await - .unwrap() - .iter() - .map(|row| Self { - guild_id, - name: row.name.clone(), - description: row.description.clone(), - commands: serde_json::from_str(&row.commands).unwrap(), - }) - .collect::>() - } + pub commands: Vec>, } diff --git a/src/models/mod.rs b/src/models/mod.rs index cf509c6..cd33af0 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -5,62 +5,47 @@ pub mod timer; pub mod user_data; use chrono_tz::Tz; -use serenity::{ - async_trait, - model::id::{ChannelId, UserId}, - prelude::Context, -}; +use poise::serenity::{async_trait, model::id::UserId}; use crate::{ models::{channel_data::ChannelData, user_data::UserData}, - SQLPool, + Context, }; #[async_trait] pub trait CtxData { - async fn user_data + Send + Sync>( + async fn user_data + Send>( &self, user_id: U, ) -> Result>; - async fn timezone + Send + Sync>(&self, user_id: U) -> Tz; + async fn author_data(&self) -> Result>; - async fn channel_data + Send + Sync>( - &self, - channel_id: C, - ) -> Result>; + async fn timezone(&self) -> Tz; + + async fn channel_data(&self) -> Result>; } #[async_trait] -impl CtxData for Context { - async fn user_data + Send + Sync>( +impl CtxData for Context<'_> { + async fn user_data + Send>( &self, user_id: U, ) -> Result> { - let user_id = user_id.into(); - let pool = self.data.read().await.get::().cloned().unwrap(); - - let user = user_id.to_user(self).await.unwrap(); - - UserData::from_user(&user, &self, &pool).await + UserData::from_user(user_id, &self.discord(), &self.data().database).await } - async fn timezone + Send + Sync>(&self, user_id: U) -> Tz { - let user_id = user_id.into(); - let pool = self.data.read().await.get::().cloned().unwrap(); - - UserData::timezone_of(user_id, &pool).await + async fn author_data(&self) -> Result> { + UserData::from_user(&self.author().id, &self.discord(), &self.data().database).await } - async fn channel_data + Send + Sync>( - &self, - channel_id: C, - ) -> Result> { - let channel_id = channel_id.into(); - let pool = self.data.read().await.get::().cloned().unwrap(); + async fn timezone(&self) -> Tz { + UserData::timezone_of(self.author().id, &self.data().database).await + } - let channel = channel_id.to_channel_cached(&self).unwrap(); + async fn channel_data(&self) -> Result> { + let channel = self.channel_id().to_channel_cached(&self.discord()).unwrap(); - ChannelData::from_channel(&channel, &pool).await + ChannelData::from_channel(&channel, &self.data().database).await } } diff --git a/src/models/reminder/builder.rs b/src/models/reminder/builder.rs index 1a2b5d9..de53b81 100644 --- a/src/models/reminder/builder.rs +++ b/src/models/reminder/builder.rs @@ -2,8 +2,7 @@ use std::{collections::HashSet, fmt::Display}; use chrono::{Duration, NaiveDateTime, Utc}; use chrono_tz::Tz; -use serenity::{ - client::Context, +use poise::serenity::{ http::CacheHttp, model::{ channel::GuildChannel, @@ -15,15 +14,14 @@ use serenity::{ use sqlx::MySqlPool; use crate::{ - consts, - consts::{DAY, MAX_TIME, MIN_INTERVAL}, + consts::{DAY, DEFAULT_AVATAR, MAX_TIME, MIN_INTERVAL}, interval_parser::Interval, models::{ channel_data::ChannelData, reminder::{content::Content, errors::ReminderError, helper::generate_uid, Reminder}, user_data::UserData, }, - SQLPool, + Context, }; async fn create_webhook( @@ -31,7 +29,7 @@ async fn create_webhook( channel: GuildChannel, name: impl Display, ) -> SerenityResult { - channel.create_webhook_with_avatar(ctx.http(), name, consts::DEFAULT_AVATAR.clone()).await + channel.create_webhook_with_avatar(ctx.http(), name, DEFAULT_AVATAR.clone()).await } #[derive(Hash, PartialEq, Eq)] @@ -145,7 +143,7 @@ pub struct MultiReminderBuilder<'a> { expires: Option, content: Content, set_by: Option, - ctx: &'a Context, + ctx: &'a Context<'a>, guild_id: Option, } @@ -210,8 +208,6 @@ impl<'a> MultiReminderBuilder<'a> { } pub async fn build(self) -> (HashSet, HashSet) { - let pool = self.ctx.data.read().await.get::().cloned().unwrap(); - let mut errors = HashSet::new(); let mut ok_locs = HashSet::new(); @@ -225,12 +221,17 @@ impl<'a> MultiReminderBuilder<'a> { for scope in self.scopes { let db_channel_id = match scope { ReminderScope::User(user_id) => { - if let Ok(user) = UserId(user_id).to_user(&self.ctx).await { - let user_data = - UserData::from_user(&user, &self.ctx, &pool).await.unwrap(); + if let Ok(user) = UserId(user_id).to_user(&self.ctx.discord()).await { + let user_data = UserData::from_user( + &user, + &self.ctx.discord(), + &self.ctx.data().database, + ) + .await + .unwrap(); if let Some(guild_id) = self.guild_id { - if guild_id.member(&self.ctx, user).await.is_err() { + if guild_id.member(&self.ctx.discord(), user).await.is_err() { Err(ReminderError::InvalidTag) } else { Ok(user_data.dm_channel) @@ -243,26 +244,36 @@ impl<'a> MultiReminderBuilder<'a> { } } ReminderScope::Channel(channel_id) => { - let channel = ChannelId(channel_id).to_channel(&self.ctx).await.unwrap(); + let channel = + ChannelId(channel_id).to_channel(&self.ctx.discord()).await.unwrap(); if let Some(guild_channel) = channel.clone().guild() { if Some(guild_channel.guild_id) != self.guild_id { Err(ReminderError::InvalidTag) } else { let mut channel_data = - ChannelData::from_channel(&channel, &pool).await.unwrap(); + ChannelData::from_channel(&channel, &self.ctx.data().database) + .await + .unwrap(); if channel_data.webhook_id.is_none() || channel_data.webhook_token.is_none() { - match create_webhook(&self.ctx, guild_channel, "Reminder").await + match create_webhook( + &self.ctx.discord(), + guild_channel, + "Reminder", + ) + .await { Ok(webhook) => { channel_data.webhook_id = Some(webhook.id.as_u64().to_owned()); channel_data.webhook_token = webhook.token; - channel_data.commit_changes(&pool).await; + channel_data + .commit_changes(&self.ctx.data().database) + .await; Ok(channel_data.id) } @@ -282,7 +293,7 @@ impl<'a> MultiReminderBuilder<'a> { match db_channel_id { Ok(c) => { let builder = ReminderBuilder { - pool: pool.clone(), + pool: self.ctx.data().database.clone(), uid: generate_uid(), channel: c, utc_time: self.utc_time, diff --git a/src/models/reminder/look_flags.rs b/src/models/reminder/look_flags.rs index 5568f85..a2a87f0 100644 --- a/src/models/reminder/look_flags.rs +++ b/src/models/reminder/look_flags.rs @@ -1,6 +1,6 @@ +use poise::serenity::model::id::ChannelId; use serde::{Deserialize, Serialize}; use serde_repr::*; -use serenity::model::id::ChannelId; #[derive(Serialize_repr, Deserialize_repr, Copy, Clone, Debug)] #[repr(u8)] diff --git a/src/models/reminder/mod.rs b/src/models/reminder/mod.rs index 5b856fe..6b46a74 100644 --- a/src/models/reminder/mod.rs +++ b/src/models/reminder/mod.rs @@ -6,15 +6,12 @@ pub mod look_flags; use chrono::{NaiveDateTime, TimeZone}; use chrono_tz::Tz; -use serenity::{ - client::Context, - model::id::{ChannelId, GuildId, UserId}, -}; -use sqlx::MySqlPool; +use poise::serenity::model::id::{ChannelId, GuildId, UserId}; +use sqlx::Executor; use crate::{ models::reminder::look_flags::{LookFlags, TimeDisplayType}, - SQLPool, + Context, Data, Database, }; #[derive(Debug, Clone)] @@ -33,7 +30,10 @@ pub struct Reminder { } impl Reminder { - pub async fn from_uid(pool: &MySqlPool, uid: String) -> Option { + pub async fn from_uid( + pool: impl Executor<'_, Database = Database>, + uid: String, + ) -> Option { sqlx::query_as_unchecked!( Self, " @@ -70,12 +70,10 @@ WHERE } pub async fn from_channel>( - ctx: &Context, + ctx: &Context<'_>, channel_id: C, flags: &LookFlags, ) -> Vec { - let pool = ctx.data.read().await.get::().cloned().unwrap(); - let enabled = if flags.show_disabled { "0,1" } else { "1" }; let channel_id = channel_id.into(); @@ -113,16 +111,18 @@ ORDER BY channel_id.as_u64(), enabled, ) - .fetch_all(&pool) + .fetch_all(&ctx.data().database) .await .unwrap() } - pub async fn from_guild(ctx: &Context, guild_id: Option, user: UserId) -> Vec { - let pool = ctx.data.read().await.get::().cloned().unwrap(); - + pub async fn from_guild( + ctx: &Context<'_>, + guild_id: Option, + user: UserId, + ) -> Vec { if let Some(guild_id) = guild_id { - let guild_opt = guild_id.to_guild_cached(&ctx); + let guild_opt = guild_id.to_guild_cached(&ctx.discord()); if let Some(guild) = guild_opt { let channels = guild @@ -163,7 +163,7 @@ WHERE ", channels ) - .fetch_all(&pool) + .fetch_all(&ctx.data().database) .await } else { sqlx::query_as_unchecked!( @@ -196,7 +196,7 @@ WHERE ", guild_id.as_u64() ) - .fetch_all(&pool) + .fetch_all(&ctx.data().database) .await } } else { @@ -230,7 +230,7 @@ WHERE ", user.as_u64() ) - .fetch_all(&pool) + .fetch_all(&ctx.data().database) .await } .unwrap() diff --git a/src/models/user_data.rs b/src/models/user_data.rs index 76d730c..8a9f188 100644 --- a/src/models/user_data.rs +++ b/src/models/user_data.rs @@ -1,9 +1,6 @@ use chrono_tz::Tz; use log::error; -use serenity::{ - http::CacheHttp, - model::{id::UserId, user::User}, -}; +use poise::serenity::{http::CacheHttp, model::id::UserId}; use sqlx::MySqlPool; use crate::consts::LOCAL_TIMEZONE; @@ -11,7 +8,6 @@ use crate::consts::LOCAL_TIMEZONE; pub struct UserData { pub id: u32, pub user: u64, - pub name: String, pub dm_channel: u32, pub timezone: String, } @@ -40,20 +36,20 @@ SELECT timezone FROM users WHERE user = ? .unwrap() } - pub async fn from_user( - user: &User, + pub async fn from_user>( + user: U, ctx: impl CacheHttp, pool: &MySqlPool, ) -> Result> { - let user_id = user.id.as_u64().to_owned(); + let user_id = user.into(); match sqlx::query_as_unchecked!( Self, " -SELECT id, user, name, dm_channel, IF(timezone IS NULL, ?, timezone) AS timezone FROM users WHERE user = ? +SELECT id, user, dm_channel, IF(timezone IS NULL, ?, timezone) AS timezone FROM users WHERE user = ? ", *LOCAL_TIMEZONE, - user_id + user_id.0 ) .fetch_one(pool) .await @@ -61,27 +57,24 @@ SELECT id, user, name, dm_channel, IF(timezone IS NULL, ?, timezone) AS timezone Ok(c) => Ok(c), Err(sqlx::Error::RowNotFound) => { - let dm_channel = user.create_dm_channel(ctx).await?; - let dm_id = dm_channel.id.as_u64().to_owned(); - + let dm_channel = user_id.create_dm_channel(ctx).await?; let pool_c = pool.clone(); sqlx::query!( " INSERT IGNORE INTO channels (channel) VALUES (?) ", - dm_id + dm_channel.id.0 ) .execute(&pool_c) .await?; sqlx::query!( " -INSERT INTO users (user, name, dm_channel, timezone) VALUES (?, ?, (SELECT id FROM channels WHERE channel = ?), ?) +INSERT INTO users (user, dm_channel, timezone) VALUES (?, (SELECT id FROM channels WHERE channel = ?), ?) ", - user_id, - user.name, - dm_id, + user_id.0, + dm_channel.id.0, *LOCAL_TIMEZONE ) .execute(&pool_c) @@ -90,9 +83,9 @@ INSERT INTO users (user, name, dm_channel, timezone) VALUES (?, ?, (SELECT id FR Ok(sqlx::query_as_unchecked!( Self, " -SELECT id, user, name, dm_channel, timezone FROM users WHERE user = ? +SELECT id, user, dm_channel, timezone FROM users WHERE user = ? ", - user_id + user_id.0 ) .fetch_one(pool) .await?) @@ -109,9 +102,8 @@ SELECT id, user, name, dm_channel, timezone FROM users WHERE user = ? pub async fn commit_changes(&self, pool: &MySqlPool) { sqlx::query!( " -UPDATE users SET name = ?, timezone = ? WHERE id = ? +UPDATE users SET timezone = ? WHERE id = ? ", - self.name, self.timezone, self.id ) diff --git a/src/utils.rs b/src/utils.rs index aa37258..e11f47c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,9 +1,39 @@ -use serenity::{ +use poise::serenity::{ + builder::CreateApplicationCommands, http::CacheHttp, model::id::{GuildId, UserId}, }; -use crate::consts::{CNC_GUILD, SUBSCRIPTION_ROLES}; +use crate::{ + consts::{CNC_GUILD, SUBSCRIPTION_ROLES}, + Data, Error, +}; + +pub async fn register_application_commands( + ctx: &poise::serenity::client::Context, + framework: &poise::Framework, + guild_id: Option, +) -> Result<(), poise::serenity::Error> { + let mut commands_builder = CreateApplicationCommands::default(); + let commands = &framework.options().commands; + for command in commands { + if let Some(slash_command) = command.create_as_slash_command() { + commands_builder.add_application_command(slash_command); + } + if let Some(context_menu_command) = command.create_as_context_menu_command() { + commands_builder.add_application_command(context_menu_command); + } + } + let commands_builder = poise::serenity::json::Value::Array(commands_builder.0); + + if let Some(guild_id) = guild_id { + ctx.http.create_guild_application_commands(guild_id.0, &commands_builder).await?; + } else { + ctx.http.create_global_application_commands(&commands_builder).await?; + } + + Ok(()) +} pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into) -> bool { if let Some(subscription_guild) = *CNC_GUILD {