From e436d9db808f7ef8fbc1146a148d68dd1ba70cb6 Mon Sep 17 00:00:00 2001 From: jellywx Date: Tue, 1 Feb 2022 01:07:12 +0000 Subject: [PATCH] moving stuff to poise --- Cargo.lock | 1162 ++++++++++++++++++++------ Cargo.toml | 22 +- command_attributes/Cargo.toml | 16 - command_attributes/src/attributes.rs | 351 -------- command_attributes/src/consts.rs | 10 - command_attributes/src/lib.rs | 321 ------- command_attributes/src/structures.rs | 331 -------- command_attributes/src/util.rs | 176 ---- src/commands/info_cmds.rs | 113 ++- src/commands/mod.rs | 4 +- src/commands/moderation_cmds.rs | 632 +++++++------- src/component_models/mod.rs | 14 +- src/component_models/pager.rs | 4 +- src/consts.rs | 4 +- src/event_handlers.rs | 83 ++ src/framework.rs | 692 --------------- src/hooks.rs | 163 ++-- src/main.rs | 366 ++------ src/models/channel_data.rs | 2 +- src/models/command_macro.rs | 189 ++++- src/models/mod.rs | 51 +- src/models/reminder/builder.rs | 35 +- src/models/reminder/look_flags.rs | 2 +- src/models/reminder/mod.rs | 26 +- src/models/user_data.rs | 36 +- src/utils.rs | 67 ++ 26 files changed, 1845 insertions(+), 3027 deletions(-) delete mode 100644 command_attributes/Cargo.toml delete mode 100644 command_attributes/src/attributes.rs delete mode 100644 command_attributes/src/consts.rs delete mode 100644 command_attributes/src/lib.rs delete mode 100644 command_attributes/src/structures.rs delete mode 100644 command_attributes/src/util.rs create mode 100644 src/event_handlers.rs delete mode 100644 src/framework.rs create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index ac9fc11..6444853 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,13 +8,23 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", + "rand_core 0.6.3", +] + [[package]] name = "ahash" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.4", "once_cell", "version_check", ] @@ -29,10 +39,28 @@ dependencies = [ ] [[package]] -name = "async-trait" -version = "0.1.51" +name = "ansi_term" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + +[[package]] +name = "async-trait" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ "proc-macro2", "quote", @@ -41,18 +69,34 @@ dependencies = [ [[package]] name = "async-tungstenite" -version = "0.15.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "742cc7dcb20b2f84a42f4691aa999070ec7e78f8e7e7438bf14be7017b44907e" +checksum = "8645e929ec7964448a901db9da30cd2ae8c7fecf4d6176af427837531dbbb63b" dependencies = [ "futures-io", "futures-util", "log", "pin-project-lite", "tokio", - "tokio-rustls", - "tungstenite", - "webpki-roots", + "tokio-rustls 0.22.0", + "tungstenite 0.13.0", + "webpki-roots 0.21.1", +] + +[[package]] +name = "async-tungstenite" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5682ea0913e5c20780fe5785abacb85a411e7437bf52a1bedb93ddb3972cb8dd" +dependencies = [ + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "tokio", + "tokio-rustls 0.23.2", + "tungstenite 0.16.0", + "webpki-roots 0.22.2", ] [[package]] @@ -61,7 +105,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616896e05fc0e2649463a93a15183c6a16bf03413a7af88ef1285ddedfa9cda5" dependencies = [ - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -75,6 +119,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "audiopus" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3743519567e9135cf6f9f1a509851cb0c8e4cb9d66feb286668afb1923bec458" +dependencies = [ + "audiopus_sys", +] + +[[package]] +name = "audiopus_sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927791de46f70facea982dbfaf19719a41ce6064443403be631a85de6a58fff9" +dependencies = [ + "log", + "pkg-config", +] + [[package]] name = "autocfg" version = "0.1.7" @@ -93,15 +156,21 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64ct" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6b4d9b1225d28d360ec6a231d65af1fd99a2a095154c8040689617290569c5c" + [[package]] name = "bigdecimal" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1e50562e37200edf7c6c43e54a08e64a5553bfb59d9c297d5572512aa517256" dependencies = [ - "num-bigint 0.3.3", + "num-bigint", "num-integer", - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -121,9 +190,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.8.0" +version = "3.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1e260c3a9040a7c19a12468758f4c16f31a81a1fe087482be9570ec864bb6c" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" [[package]] name = "byteorder" @@ -157,7 +226,7 @@ checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" dependencies = [ "libc", "num-integer", - "num-traits", + "num-traits 0.2.14", "serde", "time", "winapi", @@ -174,6 +243,21 @@ dependencies = [ "serde", ] +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "const-oid" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b" + [[package]] name = "core-foundation" version = "0.9.2" @@ -216,18 +300,18 @@ checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403" [[package]] name = "crc32fast" -version = "1.2.1" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +checksum = "a2209c310e29876f7f0b2721e7e26b84aff178aa3da5d091f9bfbf47669e60e3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ed27e177f16d65f0f0c22a213e17c696ace5dd64b14258b52f9417ccb52db4" +checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa" dependencies = [ "cfg-if", "crossbeam-utils", @@ -235,9 +319,9 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" +checksum = "b979d76c9fcb84dffc80a73f7290da0f83e4c95773494674cb44b76d13a7a110" dependencies = [ "cfg-if", "crossbeam-utils", @@ -245,14 +329,60 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db" +checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120" dependencies = [ "cfg-if", "lazy_static", ] +[[package]] +name = "crypto-bigint" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83bd3bb4314701c568e340cd8cf78c975aa0ca79e03d3f6d1677d5b0c9c0c03" +dependencies = [ + "generic-array", + "rand_core 0.6.3", + "subtle", +] + +[[package]] +name = "darling" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +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" @@ -261,9 +391,41 @@ checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c" dependencies = [ "cfg-if", "num_cpus", +] + +[[package]] +name = "dashmap" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b799062aaf67eb976af3bdca031ee6f846d2f0a5710ddbb0d2efee33f3cc4760" +dependencies = [ + "cfg-if", + "num_cpus", + "parking_lot", "serde", ] +[[package]] +name = "der" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4" +dependencies = [ + "const-oid", + "crypto-bigint", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.9.0" @@ -273,6 +435,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "discortp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb66017646a48220b5ea30d63ac18bb5952f647f1a41ed755880895125d26972" +dependencies = [ + "pnet_macros", + "pnet_macros_support", +] + [[package]] name = "dotenv" version = "0.15.0" @@ -287,13 +459,22 @@ checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "encoding_rs" -version = "0.8.29" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a74ea89a0a1b98f6332de42c95baff457ada66d1cb4030f9ff151b2041a1c746" +checksum = "7896dc8abb250ffdda33912550faa54c88ec8b998dec0b2c55ab224921ce11df" dependencies = [ "cfg-if", ] +[[package]] +name = "enum_primitive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" +dependencies = [ + "num-traits 0.1.43", +] + [[package]] name = "env_logger" version = "0.8.4" @@ -307,6 +488,15 @@ dependencies = [ "termcolor", ] +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + [[package]] name = "flate2" version = "1.0.22" @@ -319,6 +509,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.10.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d04dafd11240188e146b6f6476a898004cace3be31d4ec5e08e216bf4947ac0" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "pin-project", + "spin 0.9.2", +] + [[package]] name = "fnv" version = "1.0.7" @@ -352,12 +555,13 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca" +checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -366,9 +570,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888" +checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" dependencies = [ "futures-core", "futures-sink", @@ -376,9 +580,20 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" +checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" + +[[package]] +name = "futures-executor" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] [[package]] name = "futures-intrusive" @@ -393,18 +608,16 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377" +checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" [[package]] name = "futures-macro" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb" +checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" dependencies = [ - "autocfg 1.0.1", - "proc-macro-hack", "proc-macro2", "quote", "syn", @@ -412,23 +625,22 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11" +checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" [[package]] name = "futures-task" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99" +checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" [[package]] name = "futures-util" -version = "0.3.17" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481" +checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" dependencies = [ - "autocfg 1.0.1", "futures-channel", "futures-core", "futures-io", @@ -438,16 +650,27 @@ dependencies = [ "memchr", "pin-project-lite", "pin-utils", - "proc-macro-hack", - "proc-macro-nested", "slab", ] [[package]] -name = "generic-array" -version = "0.14.4" +name = "generator" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +checksum = "c1d9279ca822891c1a4dae06d185612cf8fc6acfe5dff37781b41297811b12ee" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "winapi", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" dependencies = [ "typenum", "version_check", @@ -466,20 +689,22 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.10.2+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] name = "h2" -version = "0.3.7" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fd819562fcebdac5afc5c113c3ec36f902840b70fd4fc458799c8ce4607ae55" +checksum = "d9f1f717ddc7b2ba36df7e871fd88db79326551d3d6f1fc406fbfd28b582ff8e" dependencies = [ "bytes", "fnv", @@ -538,13 +763,13 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" +checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03" dependencies = [ "bytes", "fnv", - "itoa", + "itoa 1.0.1", ] [[package]] @@ -566,9 +791,9 @@ checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" [[package]] name = "httpdate" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "humantime" @@ -578,9 +803,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.14" +version = "0.14.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b91bb1f221b6ea1f1e4371216b70f40748774c2fb5971b450c07773fb92d26b" +checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" dependencies = [ "bytes", "futures-channel", @@ -591,7 +816,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa", + "itoa 0.4.8", "pin-project-lite", "socket2", "tokio", @@ -602,17 +827,15 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.22.1" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" dependencies = [ - "futures-util", + "http", "hyper", - "log", - "rustls", + "rustls 0.20.2", "tokio", - "tokio-rustls", - "webpki", + "tokio-rustls 0.23.2", ] [[package]] @@ -628,6 +851,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" @@ -641,14 +870,23 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ "autocfg 1.0.1", "hashbrown", ] +[[package]] +name = "input_buffer" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f97967975f448f1a7ddb12b0bc41069d09ed6a1c161a92687e057325db35d413" +dependencies = [ + "bytes", +] + [[package]] name = "instant" version = "0.1.12" @@ -666,9 +904,9 @@ checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" [[package]] name = "itertools" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" dependencies = [ "either", ] @@ -680,10 +918,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" [[package]] -name = "js-sys" -version = "0.3.55" +name = "itoa" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "js-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" dependencies = [ "wasm-bindgen", ] @@ -694,7 +938,7 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" dependencies = [ - "spin", + "spin 0.5.2", ] [[package]] @@ -705,9 +949,9 @@ checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" [[package]] name = "libc" -version = "0.2.107" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219" +checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74" [[package]] name = "libm" @@ -717,9 +961,9 @@ checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" [[package]] name = "lock_api" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] @@ -733,6 +977,30 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "loom" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc5c7d328e32cc4954e8e01193d7f0ef5ab257b5090b70a964e099a36034309" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + [[package]] name = "matches" version = "0.1.9" @@ -799,6 +1067,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nanorand" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "729eb334247daa1803e0a094d0a5c55711b85571179f5ec6e53eccfdf7008958" +dependencies = [ + "getrandom 0.2.4", +] + [[package]] name = "native-tls" version = "0.2.8" @@ -817,6 +1094,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "nom" version = "7.1.0" @@ -845,18 +1128,7 @@ checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" dependencies = [ "autocfg 1.0.1", "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" -dependencies = [ - "autocfg 1.0.1", - "num-integer", - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -871,7 +1143,7 @@ dependencies = [ "libm", "num-integer", "num-iter", - "num-traits", + "num-traits 0.2.14", "rand 0.8.4", "smallvec", "zeroize", @@ -884,7 +1156,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" dependencies = [ "autocfg 1.0.1", - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -895,7 +1167,16 @@ checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" dependencies = [ "autocfg 1.0.1", "num-integer", - "num-traits", + "num-traits 0.2.14", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.14", ] [[package]] @@ -910,9 +1191,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", @@ -920,9 +1201,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "opaque-debug" @@ -946,15 +1227,15 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.70" +version = "0.9.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6517987b3f8226b5da3661dad65ff7f300cc59fb5ea8333ca191fc65fde3edf" +checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" dependencies = [ "autocfg 1.0.1", "cc", @@ -998,14 +1279,12 @@ dependencies = [ ] [[package]] -name = "pem" -version = "0.8.3" +name = "pem-rfc7468" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" +checksum = "84e93a3b1cc0510b03020f33f21e62acdde3dcaef432edc95bea377fbd4c2cd4" dependencies = [ - "base64", - "once_cell", - "regex", + "base64ct", ] [[package]] @@ -1015,10 +1294,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] -name = "pin-project-lite" -version = "0.2.7" +name = "pin-project" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" [[package]] name = "pin-utils" @@ -1027,43 +1326,119 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "pkg-config" -version = "0.3.22" +name = "pkcs1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" +checksum = "116bee8279d783c0cf370efa1a94632f2108e5ef0bb32df31f051647810a4e2c" +dependencies = [ + "der", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447" +dependencies = [ + "der", + "pem-rfc7468", + "pkcs1", + "spki", + "zeroize", +] + +[[package]] +name = "pkg-config" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" + +[[package]] +name = "pnet_base" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25488cd551a753dcaaa6fffc9f69a7610a412dd8954425bf7ffad5f7d1156fb8" + +[[package]] +name = "pnet_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30490e0852e58402b8fae0d39897b08a24f493023a4d6cf56b2e30f31ed57548" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "pnet_macros_support" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4714e10f30cab023005adce048f2d30dd4ac4f093662abf2220855655ef8f90" +dependencies = [ + "pnet_base", +] + +[[package]] +name = "poise" +version = "0.1.0" +source = "git+https://github.com/kangalioo/poise?branch=master#9ebc7b541b2deca8efde1c8bcbdf3d415d8e3a9a" +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#9ebc7b541b2deca8efde1c8bcbdf3d415d8e3a9a" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] [[package]] name = "ppv-lite86" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro-nested" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.10" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ "proc-macro2", ] @@ -1128,7 +1503,7 @@ version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.4", ] [[package]] @@ -1169,22 +1544,21 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" -[[package]] -name = "regex_command_attr" -version = "0.3.6" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "uuid", -] - [[package]] name = "reminder_rs" version = "1.6.0-beta2" @@ -1199,15 +1573,15 @@ dependencies = [ "levenshtein", "log", "num-integer", + "poise", "rand 0.7.3", "regex", - "regex_command_attr", "reqwest", "rmp-serde", "serde", "serde_json", "serde_repr", - "serenity", + "songbird", "sqlx", "tokio", ] @@ -1223,15 +1597,16 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.6" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66d2927ca2f685faf0fc620ac4834690d29e7abb153add10f5812eef20b5e280" +checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "futures-util", + "h2", "http", "http-body", "hyper", @@ -1246,18 +1621,20 @@ dependencies = [ "native-tls", "percent-encoding", "pin-project-lite", - "rustls", + "rustls 0.20.2", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", - "tokio-rustls", + "tokio-rustls 0.23.2", + "tokio-util", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.22.2", "winreg", ] @@ -1270,7 +1647,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", + "spin 0.5.2", "untrusted", "web-sys", "winapi", @@ -1283,7 +1660,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f55e5fa1446c4d5dd1f5daeed2a4fe193071771a2636274d0d7a3b082aa7ad6" dependencies = [ "byteorder", - "num-traits", + "num-traits 0.2.14", ] [[package]] @@ -1299,9 +1676,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0aeddcca1082112a6eeb43bf25fd7820b066aaf6eaef776e19d0a1febe38fe" +checksum = "e05c2603e2823634ab331437001b411b9ed11660fbc4066f3908c84a9439260d" dependencies = [ "byteorder", "digest", @@ -1309,10 +1686,10 @@ dependencies = [ "num-bigint-dig", "num-integer", "num-iter", - "num-traits", - "pem", + "num-traits 0.2.14", + "pkcs1", + "pkcs8", "rand 0.8.4", - "simple_asn1", "subtle", "zeroize", ] @@ -1326,15 +1703,52 @@ dependencies = [ "base64", "log", "ring", - "sct", - "webpki", + "sct 0.6.1", + "webpki 0.21.4", ] [[package]] -name = "ryu" -version = "1.0.5" +name = "rustls" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "d37e5e2290f3e040b594b1a9e04377c2c671f1a1cfd9bfdef82106ac1c113f84" +dependencies = [ + "log", + "ring", + "sct 0.7.0", + "webpki 0.22.0", +] + +[[package]] +name = "rustls-pemfile" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" +dependencies = [ + "base64", +] + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "salsa20" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecbd2eb639fd7cab5804a0837fe373cc2172d15437e804c054a9fb885cb923b0" +dependencies = [ + "cipher", + "zeroize", +] [[package]] name = "schannel" @@ -1346,6 +1760,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "scoped-tls" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1363,10 +1783,20 @@ dependencies = [ ] [[package]] -name = "security-framework" -version = "2.4.2" +name = "sct" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fed7948b6c68acbb6e20c334f55ad635dc0f75506963de4464289fbd3b051ac" dependencies = [ "bitflags", "core-foundation", @@ -1377,9 +1807,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.4.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e" +checksum = "a57321bf8bc2362081b2599912d2961fe899c0efadf1b4b2f8d48b3e253bb96c" dependencies = [ "core-foundation-sys", "libc", @@ -1387,18 +1817,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.130" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.130" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -1407,11 +1837,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.69" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e466864e431129c7e0d3476b92f20458e5879919a0596c6472738d9fa2d342f8" +checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" dependencies = [ - "itoa", + "itoa 1.0.1", "ryu", "serde", ] @@ -1429,28 +1859,28 @@ dependencies = [ [[package]] name = "serde_urlencoded" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa", + "itoa 1.0.1", "ryu", "serde", ] [[package]] name = "serenity" -version = "0.10.9" -source = "git+https://github.com/serenity-rs/serenity?branch=next#8d331b33171739ccecdc902faeefbcc7d32aa0eb" +version = "0.10.10" +source = "git+https://github.com/serenity-rs/serenity?branch=next#e7567464e09806aec8f6ee9317d33c808d39eea3" dependencies = [ "async-trait", - "async-tungstenite", + "async-tungstenite 0.16.1", "base64", "bitflags", "bytes", "chrono", - "dashmap", + "dashmap 5.0.0", "flate2", "futures", "mime", @@ -1466,6 +1896,18 @@ dependencies = [ "url", ] +[[package]] +name = "serenity-voice-model" +version = "0.1.1" +source = "git+https://github.com/serenity-rs/serenity?branch=next#e7567464e09806aec8f6ee9317d33c808d39eea3" +dependencies = [ + "bitflags", + "enum_primitive", + "serde", + "serde_json", + "serde_repr", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -1481,9 +1923,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer", "cfg-if", @@ -1492,6 +1934,15 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1501,18 +1952,6 @@ dependencies = [ "libc", ] -[[package]] -name = "simple_asn1" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eb4ea60fb301dc81dfc113df680571045d375ab7345d171c5dc7d7e13107a80" -dependencies = [ - "chrono", - "num-bigint 0.4.3", - "num-traits", - "thiserror", -] - [[package]] name = "slab" version = "0.4.5" @@ -1521,26 +1960,87 @@ checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "smallvec" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "socket2" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", "winapi", ] +[[package]] +name = "songbird" +version = "0.2.1" +source = "git+https://github.com/serenity-rs/songbird?branch=next#d3f0974412d217e7047c9681ba85d0217d79e741" +dependencies = [ + "async-trait", + "async-tungstenite 0.14.0", + "audiopus", + "byteorder", + "dashmap 4.0.2", + "derivative", + "discortp", + "flume", + "futures", + "parking_lot", + "pin-project", + "rand 0.8.4", + "serde", + "serde_json", + "serenity", + "serenity-voice-model", + "spin_sleep", + "streamcatcher", + "symphonia-core", + "tokio", + "tracing", + "tracing-futures", + "typemap_rev", + "url", + "uuid", + "xsalsa20poly1305", +] + [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin_sleep" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a98101bdc3833e192713c2af0b0dd2614f50d1cf1f7a97c5221b7aac052acc7" +dependencies = [ + "once_cell", + "winapi", +] + +[[package]] +name = "spki" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32" +dependencies = [ + "der", +] + [[package]] name = "sqlformat" version = "0.1.8" @@ -1554,9 +2054,9 @@ dependencies = [ [[package]] name = "sqlx" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7911b0031a0247af40095838002999c7a52fba29d9739e93326e71a5a1bc9d43" +checksum = "692749de69603d81e016212199d73a2e14ee20e2def7d7914919e8db5d4d48b9" dependencies = [ "sqlx-core", "sqlx-macros", @@ -1564,13 +2064,12 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aec89bfaca8f7737439bad16d52b07f1ccd0730520d3bf6ae9d069fe4b641fb1" +checksum = "518be6f6fff5ca76f985d434f9c37f3662af279642acf730388f271dff7b9016" dependencies = [ "ahash", "atoi", - "base64", "bigdecimal", "bitflags", "byteorder", @@ -1590,17 +2089,17 @@ dependencies = [ "hashlink", "hex", "indexmap", - "itoa", + "itoa 1.0.1", "libc", "log", "memchr", - "num-bigint 0.3.3", + "num-bigint", "once_cell", "parking_lot", "percent-encoding", "rand 0.8.4", "rsa", - "rustls", + "rustls 0.19.1", "sha-1", "sha2", "smallvec", @@ -1610,16 +2109,15 @@ dependencies = [ "thiserror", "tokio-stream", "url", - "webpki", - "webpki-roots", - "whoami", + "webpki 0.21.4", + "webpki-roots 0.21.1", ] [[package]] name = "sqlx-macros" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584866c833511b1a152e87a7ee20dee2739746f60c858b3c5209150bc4b466f5" +checksum = "38e45140529cf1f90a5e1c2e561500ca345821a1c513652c8f486bbf07407cc8" dependencies = [ "dotenv", "either", @@ -1636,13 +2134,24 @@ dependencies = [ [[package]] name = "sqlx-rt" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d1bd069de53442e7a320f525a6d4deb8bb0621ac7a55f7eccbc2b58b57f43d0" +checksum = "8061cbaa91ee75041514f67a09398c65a64efed72c90151ecd47593bad53da99" dependencies = [ "once_cell", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", +] + +[[package]] +name = "streamcatcher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71664755c349abb0758fda6218fb2d2391ca2a73f9302c03b145491db4fcea29" +dependencies = [ + "crossbeam-utils", + "futures-util", + "loom", ] [[package]] @@ -1655,6 +2164,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" @@ -1662,10 +2177,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] -name = "syn" -version = "1.0.81" +name = "symphonia-core" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +checksum = "3e32b956473d98c601dac257fab8a7700d42e49fdd7a33432dd5dc7fdd2448dd" +dependencies = [ + "arrayvec", + "bitflags", + "byteorder", + "lazy_static", + "log", +] + +[[package]] +name = "syn" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ "proc-macro2", "quote", @@ -1686,13 +2214,13 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" dependencies = [ "cfg-if", + "fastrand", "libc", - "rand 0.8.4", "redox_syscall", "remove_dir_all", "winapi", @@ -1727,6 +2255,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + [[package]] name = "time" version = "0.1.43" @@ -1754,11 +2291,10 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.13.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "588b2d10a336da58d877567cd8fb8a14b463e2104910f8132cd054b4b96e29ee" +checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" dependencies = [ - "autocfg 1.0.1", "bytes", "libc", "memchr", @@ -1774,9 +2310,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.5.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114383b041aa6212c579467afa0075fbbdd0718de036100bc0ba7961d8cb9095" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote", @@ -1799,9 +2335,20 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ - "rustls", + "rustls 0.19.1", "tokio", - "webpki", + "webpki 0.21.4", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" +dependencies = [ + "rustls 0.20.2", + "tokio", + "webpki 0.22.0", ] [[package]] @@ -1868,6 +2415,45 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-log" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5312f325fe3588e277415f5a6cca1f4ccad0f248c4cd5a4bd33032d7286abc22" +dependencies = [ + "ansi_term", + "lazy_static", + "matchers", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "try-lock" version = "0.2.3" @@ -1876,9 +2462,32 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.15.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "983d40747bce878d2fb67d910dcb8bd3eca2b2358540c3cc1b98c027407a3ae3" +checksum = "5fe8dada8c1a3aeca77d6b51a4f1314e0f4b8e438b7b1b71e3ddaca8080e4093" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "input_buffer", + "log", + "rand 0.8.4", + "rustls 0.19.1", + "sha-1", + "thiserror", + "url", + "utf-8", + "webpki 0.21.4", + "webpki-roots 0.21.1", +] + +[[package]] +name = "tungstenite" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ad3713a14ae247f22a728a0456a545df14acf3867f905adff84be99e23b3ad1" dependencies = [ "base64", "byteorder", @@ -1887,12 +2496,12 @@ dependencies = [ "httparse", "log", "rand 0.8.4", - "rustls", + "rustls 0.20.2", "sha-1", "thiserror", "url", "utf-8", - "webpki", + "webpki 0.22.0", ] [[package]] @@ -1903,9 +2512,9 @@ checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" [[package]] name = "typenum" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "unicase" @@ -1949,6 +2558,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -1980,7 +2599,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.3", + "getrandom 0.2.4", ] [[package]] @@ -1991,9 +2610,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" @@ -2019,9 +2638,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2029,9 +2648,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" dependencies = [ "bumpalo", "lazy_static", @@ -2044,9 +2663,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" dependencies = [ "cfg-if", "js-sys", @@ -2056,9 +2675,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2066,9 +2685,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" dependencies = [ "proc-macro2", "quote", @@ -2079,15 +2698,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" [[package]] name = "web-sys" -version = "0.3.55" +version = "0.3.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" dependencies = [ "js-sys", "wasm-bindgen", @@ -2103,23 +2722,32 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "webpki-roots" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940" dependencies = [ - "webpki", + "webpki 0.21.4", ] [[package]] -name = "whoami" -version = "1.2.0" +name = "webpki-roots" +version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33ac5ee236a4efbf2c98967e12c6cc0c51d93a744159a52957ba206ae6ef5f7" +checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449" dependencies = [ - "wasm-bindgen", - "web-sys", + "webpki 0.22.0", ] [[package]] @@ -2162,6 +2790,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "xsalsa20poly1305" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e0f69b133860e3614a4d4fdd6f0d7fe3219e9d67a7e8cd537676a4ebc8313db" +dependencies = [ + "aead", + "poly1305", + "rand_core 0.6.3", + "salsa20", + "subtle", + "zeroize", +] + [[package]] name = "zeroize" version = "1.3.0" @@ -2173,9 +2815,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.2.2" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65f1a51723ec88c66d5d1fe80c841f17f63587d6691901d66be9bec6c3b51f73" +checksum = "81e8f13fef10b63c06356d65d416b070798ddabcadc10d3ece0c5be9b3c7eddb" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 16cf289..2ef7747 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ authors = ["jellywx "] edition = "2018" [dependencies] +songbird = { git = "https://github.com/serenity-rs/songbird", branch = "next" } +poise = { git = "https://github.com/kangalioo/poise", branch = "master" } dotenv = "0.15" humantime = "2.1" tokio = { version = "1", features = ["process", "full"] } @@ -24,23 +26,3 @@ rand = "0.7" levenshtein = "1.0" sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono"]} base64 = "0.13.0" - -[dependencies.regex_command_attr] -path = "command_attributes" - -[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/command_attributes/Cargo.toml b/command_attributes/Cargo.toml deleted file mode 100644 index a4ce1d3..0000000 --- a/command_attributes/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] -name = "regex_command_attr" -version = "0.3.6" -authors = ["acdenisSK ", "jellywx "] -edition = "2018" -description = "Procedural macros for command creation for the Serenity library." -license = "ISC" - -[lib] -proc-macro = true - -[dependencies] -quote = "^1.0" -syn = { version = "^1.0", features = ["full", "derive", "extra-traits"] } -proc-macro2 = "1.0" -uuid = { version = "0.8", features = ["v4"] } diff --git a/command_attributes/src/attributes.rs b/command_attributes/src/attributes.rs deleted file mode 100644 index 1293186..0000000 --- a/command_attributes/src/attributes.rs +++ /dev/null @@ -1,351 +0,0 @@ -use std::fmt::{self, Write}; - -use proc_macro2::Span; -use syn::{ - parse::{Error, Result}, - spanned::Spanned, - Attribute, Ident, Lit, LitStr, Meta, NestedMeta, Path, -}; - -use crate::{ - structures::{ApplicationCommandOptionType, Arg}, - util::{AsOption, LitExt}, -}; - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum ValueKind { - // #[] - Name, - - // #[ = ] - Equals, - - // #[([, , , ...])] - List, - - // #[([ = , = , ...])] - EqualsList, - - // #[()] - SingleList, -} - -impl fmt::Display for ValueKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ValueKind::Name => f.pad("`#[]`"), - ValueKind::Equals => f.pad("`#[ = ]`"), - ValueKind::List => f.pad("`#[([, , , ...])]`"), - ValueKind::EqualsList => { - f.pad("`#[([ = , = , ...])]`") - } - ValueKind::SingleList => f.pad("`#[()]`"), - } - } -} - -fn to_ident(p: Path) -> Result { - if p.segments.is_empty() { - return Err(Error::new(p.span(), "cannot convert an empty path to an identifier")); - } - - if p.segments.len() > 1 { - return Err(Error::new(p.span(), "the path must not have more than one segment")); - } - - if !p.segments[0].arguments.is_empty() { - return Err(Error::new(p.span(), "the singular path segment must not have any arguments")); - } - - Ok(p.segments[0].ident.clone()) -} - -#[derive(Debug)] -pub struct Values { - pub name: Ident, - pub literals: Vec<(Option, Lit)>, - pub kind: ValueKind, - pub span: Span, -} - -impl Values { - #[inline] - pub fn new( - name: Ident, - kind: ValueKind, - literals: Vec<(Option, Lit)>, - span: Span, - ) -> Self { - Values { name, literals, kind, span } - } -} - -pub fn parse_values(attr: &Attribute) -> Result { - fn is_list_or_named_list(meta: &NestedMeta) -> ValueKind { - match meta { - // catch if the nested value is a literal value - NestedMeta::Lit(_) => ValueKind::List, - // catch if the nested value is a meta value - NestedMeta::Meta(m) => match m { - // path => some quoted value - Meta::Path(_) => ValueKind::List, - Meta::List(_) | Meta::NameValue(_) => ValueKind::EqualsList, - }, - } - } - - let meta = attr.parse_meta()?; - - match meta { - Meta::Path(path) => { - let name = to_ident(path)?; - - Ok(Values::new(name, ValueKind::Name, Vec::new(), attr.span())) - } - Meta::List(meta) => { - let name = to_ident(meta.path)?; - let nested = meta.nested; - - if nested.is_empty() { - return Err(Error::new(attr.span(), "list cannot be empty")); - } - - if is_list_or_named_list(nested.first().unwrap()) == ValueKind::List { - let mut lits = Vec::with_capacity(nested.len()); - - for meta in nested { - match meta { - // catch if the nested value is a literal value - NestedMeta::Lit(l) => lits.push((None, l)), - // catch if the nested value is a meta value - NestedMeta::Meta(m) => match m { - // path => some quoted value - Meta::Path(path) => { - let i = to_ident(path)?; - lits.push((None, Lit::Str(LitStr::new(&i.to_string(), i.span())))) - } - Meta::List(_) | Meta::NameValue(_) => { - return Err(Error::new(attr.span(), "cannot nest a list; only accept literals and identifiers at this level")) - } - }, - } - } - - let kind = if lits.len() == 1 { ValueKind::SingleList } else { ValueKind::List }; - - Ok(Values::new(name, kind, lits, attr.span())) - } else { - let mut lits = Vec::with_capacity(nested.len()); - - for meta in nested { - match meta { - // catch if the nested value is a literal value - NestedMeta::Lit(_) => { - return Err(Error::new(attr.span(), "key-value pairs expected")) - } - // catch if the nested value is a meta value - NestedMeta::Meta(m) => match m { - Meta::NameValue(n) => { - let name = to_ident(n.path)?.to_string(); - let value = n.lit; - - lits.push((Some(name), value)); - } - Meta::List(_) | Meta::Path(_) => { - return Err(Error::new(attr.span(), "key-value pairs expected")) - } - }, - } - } - - Ok(Values::new(name, ValueKind::EqualsList, lits, attr.span())) - } - } - Meta::NameValue(meta) => { - let name = to_ident(meta.path)?; - let lit = meta.lit; - - Ok(Values::new(name, ValueKind::Equals, vec![(None, lit)], attr.span())) - } - } -} - -#[derive(Debug, Clone)] -struct DisplaySlice<'a, T>(&'a [T]); - -impl<'a, T: fmt::Display> fmt::Display for DisplaySlice<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut iter = self.0.iter().enumerate(); - - match iter.next() { - None => f.write_str("nothing")?, - Some((idx, elem)) => { - write!(f, "{}: {}", idx, elem)?; - - for (idx, elem) in iter { - f.write_char('\n')?; - write!(f, "{}: {}", idx, elem)?; - } - } - } - - Ok(()) - } -} - -#[inline] -fn is_form_acceptable(expect: &[ValueKind], kind: ValueKind) -> bool { - if expect.contains(&ValueKind::List) && kind == ValueKind::SingleList { - true - } else { - expect.contains(&kind) - } -} - -#[inline] -fn validate(values: &Values, forms: &[ValueKind]) -> Result<()> { - if !is_form_acceptable(forms, values.kind) { - return Err(Error::new( - values.span, - // Using the `_args` version here to avoid an allocation. - format_args!("the attribute must be in of these forms:\n{}", DisplaySlice(forms)), - )); - } - - Ok(()) -} - -#[inline] -pub fn parse(values: Values) -> Result { - T::parse(values) -} - -pub trait AttributeOption: Sized { - fn parse(values: Values) -> Result; -} - -impl AttributeOption for Vec { - fn parse(values: Values) -> Result { - validate(&values, &[ValueKind::List])?; - - Ok(values.literals.into_iter().map(|(_, l)| l.to_str()).collect()) - } -} - -impl AttributeOption for String { - #[inline] - fn parse(values: Values) -> Result { - validate(&values, &[ValueKind::Equals, ValueKind::SingleList])?; - - Ok(values.literals[0].1.to_str()) - } -} - -impl AttributeOption for bool { - #[inline] - fn parse(values: Values) -> Result { - validate(&values, &[ValueKind::Name, ValueKind::SingleList])?; - - Ok(values.literals.get(0).map_or(true, |(_, l)| l.to_bool())) - } -} - -impl AttributeOption for Ident { - #[inline] - fn parse(values: Values) -> Result { - validate(&values, &[ValueKind::SingleList])?; - - Ok(values.literals[0].1.to_ident()) - } -} - -impl AttributeOption for Vec { - #[inline] - fn parse(values: Values) -> Result { - validate(&values, &[ValueKind::List])?; - - Ok(values.literals.into_iter().map(|(_, l)| l.to_ident()).collect()) - } -} - -impl AttributeOption for Option { - fn parse(values: Values) -> Result { - validate(&values, &[ValueKind::Name, ValueKind::Equals, ValueKind::SingleList])?; - - Ok(values.literals.get(0).map(|(_, l)| l.to_str())) - } -} - -impl AttributeOption for Arg { - fn parse(values: Values) -> Result { - validate(&values, &[ValueKind::EqualsList])?; - - let mut arg: Arg = Default::default(); - - for (key, value) in &values.literals { - match key { - Some(s) => match s.as_str() { - "name" => { - arg.name = value.to_str(); - } - "description" => { - arg.description = value.to_str(); - } - "required" => { - arg.required = value.to_bool(); - } - "kind" => arg.kind = ApplicationCommandOptionType::from_str(value.to_str()), - _ => { - return Err(Error::new(key.span(), "unexpected attribute")); - } - }, - _ => { - return Err(Error::new(key.span(), "unnamed attribute")); - } - } - } - - Ok(arg) - } -} - -impl AttributeOption for AsOption { - #[inline] - fn parse(values: Values) -> Result { - Ok(AsOption(Some(T::parse(values)?))) - } -} - -macro_rules! attr_option_num { - ($($n:ty),*) => { - $( - impl AttributeOption for $n { - fn parse(values: Values) -> Result { - validate(&values, &[ValueKind::SingleList])?; - - Ok(match &values.literals[0].1 { - Lit::Int(l) => l.base10_parse::<$n>()?, - l => { - let s = l.to_str(); - // Use `as_str` to guide the compiler to use `&str`'s parse method. - // We don't want to use our `parse` method here (`impl AttributeOption for String`). - match s.as_str().parse::<$n>() { - Ok(n) => n, - Err(_) => return Err(Error::new(l.span(), "invalid integer")), - } - } - }) - } - } - - impl AttributeOption for Option<$n> { - #[inline] - fn parse(values: Values) -> Result { - <$n as AttributeOption>::parse(values).map(Some) - } - } - )* - } -} - -attr_option_num!(u16, u32, usize); diff --git a/command_attributes/src/consts.rs b/command_attributes/src/consts.rs deleted file mode 100644 index 8c334b4..0000000 --- a/command_attributes/src/consts.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod suffixes { - pub const COMMAND: &str = "COMMAND"; - pub const ARG: &str = "ARG"; - pub const SUBCOMMAND: &str = "SUBCOMMAND"; - pub const SUBCOMMAND_GROUP: &str = "GROUP"; - pub const CHECK: &str = "CHECK"; - pub const HOOK: &str = "HOOK"; -} - -pub use self::suffixes::*; diff --git a/command_attributes/src/lib.rs b/command_attributes/src/lib.rs deleted file mode 100644 index cce792e..0000000 --- a/command_attributes/src/lib.rs +++ /dev/null @@ -1,321 +0,0 @@ -#![deny(rust_2018_idioms)] -#![deny(broken_intra_doc_links)] - -use proc_macro::TokenStream; -use proc_macro2::Ident; -use quote::quote; -use syn::{parse::Error, parse_macro_input, parse_quote, spanned::Spanned, Lit, Type}; -use uuid::Uuid; - -pub(crate) mod attributes; -pub(crate) mod consts; -pub(crate) mod structures; - -#[macro_use] -pub(crate) mod util; - -use attributes::*; -use consts::*; -use structures::*; -use util::*; - -macro_rules! match_options { - ($v:expr, $values:ident, $options:ident, $span:expr => [$($name:ident);*]) => { - match $v { - $( - stringify!($name) => $options.$name = propagate_err!($crate::attributes::parse($values)), - )* - _ => { - return Error::new($span, format_args!("invalid attribute: {:?}", $v)) - .to_compile_error() - .into(); - }, - } - }; -} - -#[proc_macro_attribute] -pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream { - enum LastItem { - Fun, - SubFun, - SubGroup, - SubGroupFun, - } - - let mut fun = parse_macro_input!(input as CommandFun); - - let _name = if !attr.is_empty() { - parse_macro_input!(attr as Lit).to_str() - } else { - fun.name.to_string() - }; - - let mut hooks: Vec = Vec::new(); - let mut options = Options::new(); - let mut last_desc = LastItem::Fun; - - for attribute in &fun.attributes { - let span = attribute.span(); - let values = propagate_err!(parse_values(attribute)); - - let name = values.name.to_string(); - let name = &name[..]; - - match name { - "subcommand" => { - let new_subcommand = Subcommand::new(propagate_err!(attributes::parse(values))); - - if let Some(subcommand_group) = options.subcommand_groups.last_mut() { - last_desc = LastItem::SubGroupFun; - subcommand_group.subcommands.push(new_subcommand); - } else { - last_desc = LastItem::SubFun; - options.subcommands.push(new_subcommand); - } - } - "subcommandgroup" => { - let new_group = SubcommandGroup::new(propagate_err!(attributes::parse(values))); - last_desc = LastItem::SubGroup; - - options.subcommand_groups.push(new_group); - } - "arg" => { - let arg = propagate_err!(attributes::parse(values)); - - match last_desc { - LastItem::Fun => { - options.cmd_args.push(arg); - } - LastItem::SubFun => { - options.subcommands.last_mut().unwrap().cmd_args.push(arg); - } - LastItem::SubGroup => { - panic!("Argument not expected under subcommand group"); - } - LastItem::SubGroupFun => { - options - .subcommand_groups - .last_mut() - .unwrap() - .subcommands - .last_mut() - .unwrap() - .cmd_args - .push(arg); - } - } - } - "example" => { - options.examples.push(propagate_err!(attributes::parse(values))); - } - "description" => { - let line: String = propagate_err!(attributes::parse(values)); - - match last_desc { - LastItem::Fun => { - util::append_line(&mut options.description, line); - } - LastItem::SubFun => { - util::append_line( - &mut options.subcommands.last_mut().unwrap().description, - line, - ); - } - LastItem::SubGroup => { - util::append_line( - &mut options.subcommand_groups.last_mut().unwrap().description, - line, - ); - } - LastItem::SubGroupFun => { - util::append_line( - &mut options - .subcommand_groups - .last_mut() - .unwrap() - .subcommands - .last_mut() - .unwrap() - .description, - line, - ); - } - } - } - "hook" => { - hooks.push(propagate_err!(attributes::parse(values))); - } - _ => { - match_options!(name, values, options, span => [ - aliases; - group; - can_blacklist; - supports_dm - ]); - } - } - } - - let Options { - aliases, - description, - group, - examples, - can_blacklist, - supports_dm, - mut cmd_args, - mut subcommands, - mut subcommand_groups, - } = options; - - let visibility = fun.visibility; - let name = fun.name.clone(); - let body = fun.body; - - let root_ident = name.with_suffix(COMMAND); - - let command_path = quote!(crate::framework::Command); - - populate_fut_lifetimes_on_refs(&mut fun.args); - - let mut subcommand_group_idents = subcommand_groups - .iter() - .map(|subcommand| { - root_ident - .with_suffix(subcommand.name.replace("-", "_").as_str()) - .with_suffix(SUBCOMMAND_GROUP) - }) - .collect::>(); - - let mut subcommand_idents = subcommands - .iter() - .map(|subcommand| { - root_ident - .with_suffix(subcommand.name.replace("-", "_").as_str()) - .with_suffix(SUBCOMMAND) - }) - .collect::>(); - - let mut arg_idents = cmd_args - .iter() - .map(|arg| root_ident.with_suffix(arg.name.replace("-", "_").as_str()).with_suffix(ARG)) - .collect::>(); - - let mut tokens = quote! {}; - - tokens.extend( - subcommand_groups - .iter_mut() - .zip(subcommand_group_idents.iter()) - .map(|(group, group_ident)| group.as_tokens(group_ident)) - .fold(quote! {}, |mut a, b| { - a.extend(b); - a - }), - ); - - tokens.extend( - subcommands - .iter_mut() - .zip(subcommand_idents.iter()) - .map(|(subcommand, sc_ident)| subcommand.as_tokens(sc_ident)) - .fold(quote! {}, |mut a, b| { - a.extend(b); - a - }), - ); - - tokens.extend( - cmd_args.iter_mut().zip(arg_idents.iter()).map(|(arg, ident)| arg.as_tokens(ident)).fold( - quote! {}, - |mut a, b| { - a.extend(b); - a - }, - ), - ); - - arg_idents.append(&mut subcommand_group_idents); - arg_idents.append(&mut subcommand_idents); - - let args = fun.args; - - let variant = if args.len() == 2 { - quote!(crate::framework::CommandFnType::Multi) - } else { - let string: Type = parse_quote!(String); - - let final_arg = args.get(2).unwrap(); - - if final_arg.kind == string { - quote!(crate::framework::CommandFnType::Text) - } else { - quote!(crate::framework::CommandFnType::Slash) - } - }; - - tokens.extend(quote! { - #[allow(missing_docs)] - pub static #root_ident: #command_path = #command_path { - fun: #variant(#name), - names: &[#_name, #(#aliases),*], - desc: #description, - group: #group, - examples: &[#(#examples),*], - can_blacklist: #can_blacklist, - supports_dm: #supports_dm, - args: &[#(&#arg_idents),*], - hooks: &[#(&#hooks),*], - }; - - #[allow(missing_docs)] - #visibility fn #name<'fut> (#(#args),*) -> ::serenity::futures::future::BoxFuture<'fut, ()> { - use ::serenity::futures::future::FutureExt; - - async move { - #(#body)*; - }.boxed() - } - }); - - tokens.into() -} - -#[proc_macro_attribute] -pub fn check(_attr: TokenStream, input: TokenStream) -> TokenStream { - let mut fun = parse_macro_input!(input as CommandFun); - - let n = fun.name.clone(); - let name = n.with_suffix(HOOK); - let fn_name = n.with_suffix(CHECK); - let visibility = fun.visibility; - - let body = fun.body; - let ret = fun.ret; - populate_fut_lifetimes_on_refs(&mut fun.args); - let args = fun.args; - - let hook_path = quote!(crate::framework::Hook); - let uuid = Uuid::new_v4().as_u128(); - - (quote! { - #[allow(missing_docs)] - #visibility fn #fn_name<'fut>(#(#args),*) -> ::serenity::futures::future::BoxFuture<'fut, #ret> { - use ::serenity::futures::future::FutureExt; - - async move { - let _output: #ret = { #(#body)* }; - #[allow(unreachable_code)] - _output - }.boxed() - } - - #[allow(missing_docs)] - pub static #name: #hook_path = #hook_path { - fun: #fn_name, - uuid: #uuid, - }; - }) - .into() -} diff --git a/command_attributes/src/structures.rs b/command_attributes/src/structures.rs deleted file mode 100644 index f77ce46..0000000 --- a/command_attributes/src/structures.rs +++ /dev/null @@ -1,331 +0,0 @@ -use proc_macro2::TokenStream as TokenStream2; -use quote::{quote, ToTokens}; -use syn::{ - braced, - parse::{Error, Parse, ParseStream, Result}, - spanned::Spanned, - Attribute, Block, FnArg, Ident, Pat, ReturnType, Stmt, Token, Type, Visibility, -}; - -use crate::{ - consts::{ARG, SUBCOMMAND}, - util::{Argument, IdentExt2, Parenthesised}, -}; - -fn parse_argument(arg: FnArg) -> Result { - match arg { - FnArg::Typed(typed) => { - let pat = typed.pat; - let kind = typed.ty; - - match *pat { - Pat::Ident(id) => { - let name = id.ident; - let mutable = id.mutability; - - Ok(Argument { mutable, name, kind: *kind }) - } - Pat::Wild(wild) => { - let token = wild.underscore_token; - - let name = Ident::new("_", token.spans[0]); - - Ok(Argument { mutable: None, name, kind: *kind }) - } - _ => Err(Error::new(pat.span(), format_args!("unsupported pattern: {:?}", pat))), - } - } - FnArg::Receiver(_) => { - Err(Error::new(arg.span(), format_args!("`self` arguments are prohibited: {:?}", arg))) - } - } -} - -#[derive(Debug)] -pub struct CommandFun { - /// `#[...]`-style attributes. - pub attributes: Vec, - /// Populated cooked attributes. These are attributes outside of the realm of this crate's procedural macros - /// and will appear in generated output. - pub visibility: Visibility, - pub name: Ident, - pub args: Vec, - pub ret: Type, - pub body: Vec, -} - -impl Parse for CommandFun { - fn parse(input: ParseStream<'_>) -> Result { - let attributes = input.call(Attribute::parse_outer)?; - - let visibility = input.parse::()?; - - input.parse::()?; - - input.parse::()?; - let name = input.parse()?; - - // (...) - let Parenthesised(args) = input.parse::>()?; - - let ret = match input.parse::()? { - ReturnType::Type(_, t) => (*t).clone(), - ReturnType::Default => Type::Verbatim(quote!(())), - }; - - // { ... } - let bcont; - braced!(bcont in input); - let body = bcont.call(Block::parse_within)?; - - let args = args.into_iter().map(parse_argument).collect::>>()?; - - Ok(Self { attributes, visibility, name, args, ret, body }) - } -} - -impl ToTokens for CommandFun { - fn to_tokens(&self, stream: &mut TokenStream2) { - let Self { attributes: _, visibility, name, args, ret, body } = self; - - stream.extend(quote! { - #visibility async fn #name (#(#args),*) -> #ret { - #(#body)* - } - }); - } -} - -#[derive(Debug)] -pub(crate) enum ApplicationCommandOptionType { - SubCommand, - SubCommandGroup, - String, - Integer, - Boolean, - User, - Channel, - Role, - Mentionable, - Number, - Unknown, -} - -impl ApplicationCommandOptionType { - pub fn from_str(s: String) -> Self { - match s.as_str() { - "SubCommand" => Self::SubCommand, - "SubCommandGroup" => Self::SubCommandGroup, - "String" => Self::String, - "Integer" => Self::Integer, - "Boolean" => Self::Boolean, - "User" => Self::User, - "Channel" => Self::Channel, - "Role" => Self::Role, - "Mentionable" => Self::Mentionable, - "Number" => Self::Number, - _ => Self::Unknown, - } - } -} - -impl ToTokens for ApplicationCommandOptionType { - fn to_tokens(&self, stream: &mut TokenStream2) { - let path = quote!( - serenity::model::interactions::application_command::ApplicationCommandOptionType - ); - let variant = match self { - ApplicationCommandOptionType::SubCommand => quote!(SubCommand), - ApplicationCommandOptionType::SubCommandGroup => quote!(SubCommandGroup), - ApplicationCommandOptionType::String => quote!(String), - ApplicationCommandOptionType::Integer => quote!(Integer), - ApplicationCommandOptionType::Boolean => quote!(Boolean), - ApplicationCommandOptionType::User => quote!(User), - ApplicationCommandOptionType::Channel => quote!(Channel), - ApplicationCommandOptionType::Role => quote!(Role), - ApplicationCommandOptionType::Mentionable => quote!(Mentionable), - ApplicationCommandOptionType::Number => quote!(Number), - ApplicationCommandOptionType::Unknown => quote!(Unknown), - }; - - stream.extend(quote! { - #path::#variant - }); - } -} - -#[derive(Debug)] -pub(crate) struct Arg { - pub name: String, - pub description: String, - pub kind: ApplicationCommandOptionType, - pub required: bool, -} - -impl Arg { - pub fn as_tokens(&self, ident: &Ident) -> TokenStream2 { - let arg_path = quote!(crate::framework::Arg); - let Arg { name, description, kind, required } = self; - - quote! { - #[allow(missing_docs)] - pub static #ident: #arg_path = #arg_path { - name: #name, - description: #description, - kind: #kind, - required: #required, - options: &[] - }; - } - } -} - -impl Default for Arg { - fn default() -> Self { - Self { - name: String::new(), - description: String::new(), - kind: ApplicationCommandOptionType::String, - required: false, - } - } -} - -#[derive(Debug)] -pub(crate) struct Subcommand { - pub name: String, - pub description: String, - pub cmd_args: Vec, -} - -impl Subcommand { - pub fn as_tokens(&mut self, ident: &Ident) -> TokenStream2 { - let arg_path = quote!(crate::framework::Arg); - let subcommand_path = ApplicationCommandOptionType::SubCommand; - - let arg_idents = self - .cmd_args - .iter() - .map(|arg| ident.with_suffix(arg.name.as_str()).with_suffix(ARG)) - .collect::>(); - - let mut tokens = self - .cmd_args - .iter_mut() - .zip(arg_idents.iter()) - .map(|(arg, ident)| arg.as_tokens(ident)) - .fold(quote! {}, |mut a, b| { - a.extend(b); - a - }); - - let Subcommand { name, description, .. } = self; - - tokens.extend(quote! { - #[allow(missing_docs)] - pub static #ident: #arg_path = #arg_path { - name: #name, - description: #description, - kind: #subcommand_path, - required: false, - options: &[#(&#arg_idents),*], - }; - }); - - tokens - } -} - -impl Default for Subcommand { - fn default() -> Self { - Self { name: String::new(), description: String::new(), cmd_args: vec![] } - } -} - -impl Subcommand { - pub(crate) fn new(name: String) -> Self { - Self { name, ..Default::default() } - } -} - -#[derive(Debug)] -pub(crate) struct SubcommandGroup { - pub name: String, - pub description: String, - pub subcommands: Vec, -} - -impl SubcommandGroup { - pub fn as_tokens(&mut self, ident: &Ident) -> TokenStream2 { - let arg_path = quote!(crate::framework::Arg); - let subcommand_group_path = ApplicationCommandOptionType::SubCommandGroup; - - let arg_idents = self - .subcommands - .iter() - .map(|arg| { - ident - .with_suffix(self.name.as_str()) - .with_suffix(arg.name.as_str()) - .with_suffix(SUBCOMMAND) - }) - .collect::>(); - - let mut tokens = self - .subcommands - .iter_mut() - .zip(arg_idents.iter()) - .map(|(subcommand, ident)| subcommand.as_tokens(ident)) - .fold(quote! {}, |mut a, b| { - a.extend(b); - a - }); - - let SubcommandGroup { name, description, .. } = self; - - tokens.extend(quote! { - #[allow(missing_docs)] - pub static #ident: #arg_path = #arg_path { - name: #name, - description: #description, - kind: #subcommand_group_path, - required: false, - options: &[#(&#arg_idents),*], - }; - }); - - tokens - } -} - -impl Default for SubcommandGroup { - fn default() -> Self { - Self { name: String::new(), description: String::new(), subcommands: vec![] } - } -} - -impl SubcommandGroup { - pub(crate) fn new(name: String) -> Self { - Self { name, ..Default::default() } - } -} - -#[derive(Debug, Default)] -pub(crate) struct Options { - pub aliases: Vec, - pub description: String, - pub group: String, - pub examples: Vec, - pub can_blacklist: bool, - pub supports_dm: bool, - pub cmd_args: Vec, - pub subcommands: Vec, - pub subcommand_groups: Vec, -} - -impl Options { - #[inline] - pub fn new() -> Self { - Self { group: "None".to_string(), ..Default::default() } - } -} diff --git a/command_attributes/src/util.rs b/command_attributes/src/util.rs deleted file mode 100644 index 0c01e73..0000000 --- a/command_attributes/src/util.rs +++ /dev/null @@ -1,176 +0,0 @@ -use proc_macro::TokenStream; -use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::{format_ident, quote, ToTokens}; -use syn::{ - braced, bracketed, parenthesized, - parse::{Error, Parse, ParseStream, Result as SynResult}, - punctuated::Punctuated, - token::{Comma, Mut}, - Ident, Lifetime, Lit, Type, -}; - -pub trait LitExt { - fn to_str(&self) -> String; - fn to_bool(&self) -> bool; - fn to_ident(&self) -> Ident; -} - -impl LitExt for Lit { - fn to_str(&self) -> String { - match self { - Lit::Str(s) => s.value(), - Lit::ByteStr(s) => unsafe { String::from_utf8_unchecked(s.value()) }, - Lit::Char(c) => c.value().to_string(), - Lit::Byte(b) => (b.value() as char).to_string(), - _ => panic!("values must be a (byte)string or a char"), - } - } - - fn to_bool(&self) -> bool { - if let Lit::Bool(b) = self { - b.value - } else { - self.to_str() - .parse() - .unwrap_or_else(|_| panic!("expected bool from {:?}", self)) - } - } - - #[inline] - fn to_ident(&self) -> Ident { - Ident::new(&self.to_str(), self.span()) - } -} - -pub trait IdentExt2: Sized { - fn to_uppercase(&self) -> Self; - fn with_suffix(&self, suf: &str) -> Ident; -} - -impl IdentExt2 for Ident { - #[inline] - fn to_uppercase(&self) -> Self { - format_ident!("{}", self.to_string().to_uppercase()) - } - - #[inline] - fn with_suffix(&self, suffix: &str) -> Ident { - format_ident!("{}_{}", self.to_string().to_uppercase(), suffix) - } -} - -#[inline] -pub fn into_stream(e: Error) -> TokenStream { - e.to_compile_error().into() -} - -macro_rules! propagate_err { - ($res:expr) => {{ - match $res { - Ok(v) => v, - Err(e) => return $crate::util::into_stream(e), - } - }}; -} - -#[derive(Debug)] -pub struct Bracketed(pub Punctuated); - -impl Parse for Bracketed { - fn parse(input: ParseStream<'_>) -> SynResult { - let content; - bracketed!(content in input); - - Ok(Bracketed(content.parse_terminated(T::parse)?)) - } -} - -#[derive(Debug)] -pub struct Braced(pub Punctuated); - -impl Parse for Braced { - fn parse(input: ParseStream<'_>) -> SynResult { - let content; - braced!(content in input); - - Ok(Braced(content.parse_terminated(T::parse)?)) - } -} - -#[derive(Debug)] -pub struct Parenthesised(pub Punctuated); - -impl Parse for Parenthesised { - fn parse(input: ParseStream<'_>) -> SynResult { - let content; - parenthesized!(content in input); - - Ok(Parenthesised(content.parse_terminated(T::parse)?)) - } -} - -#[derive(Debug)] -pub struct AsOption(pub Option); - -impl ToTokens for AsOption { - fn to_tokens(&self, stream: &mut TokenStream2) { - match &self.0 { - Some(o) => stream.extend(quote!(Some(#o))), - None => stream.extend(quote!(None)), - } - } -} - -impl Default for AsOption { - #[inline] - fn default() -> Self { - AsOption(None) - } -} - -#[derive(Debug)] -pub struct Argument { - pub mutable: Option, - pub name: Ident, - pub kind: Type, -} - -impl ToTokens for Argument { - fn to_tokens(&self, stream: &mut TokenStream2) { - let Argument { - mutable, - name, - kind, - } = self; - - stream.extend(quote! { - #mutable #name: #kind - }); - } -} - -#[inline] -pub fn populate_fut_lifetimes_on_refs(args: &mut Vec) { - for arg in args { - if let Type::Reference(reference) = &mut arg.kind { - reference.lifetime = Some(Lifetime::new("'fut", Span::call_site())); - } - } -} - -pub fn append_line(desc: &mut String, mut line: String) { - if line.starts_with(' ') { - line.remove(0); - } - - match line.rfind("\\$") { - Some(i) => { - desc.push_str(line[..i].trim_end()); - desc.push(' '); - } - None => { - desc.push_str(&line); - desc.push('\n'); - } - } -} 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..a9de201 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..d0893c4 100644 --- a/src/commands/moderation_cmds.rs +++ b/src/commands/moderation_cmds.rs @@ -1,54 +1,64 @@ 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, 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| { + partial.contains(&tz.to_string()) + || tz.to_string().contains(&partial) + || levenshtein(&tz.to_string(), &partial) < 4 + }) + .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 +66,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 +84,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,276 +106,274 @@ 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; + ) + .color(*THEME_COLOR) + .fields(popular_timezones_iter) + .footer(|f| f.text(footer_text)) + .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee") + }) + }) + .await?; } + + Ok(()) } -#[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(); +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() +} - match args.subcommand.clone().unwrap().as_str() { - "record" => { - let guild_id = invoke.guild_id().unwrap(); +/// Record and replay command sequences +#[poise::command(slash_command, rename = "macro", check = "guild_only")] +pub async fn macro_base(_ctx: Context<'_>) -> Result<(), Error> { + Ok(()) +} - let name = args.get("name").unwrap().to_string(); +/// 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(&pool) - .await; + 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() { - 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) - }), + 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.", ) - .await; + .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 { - let macro_buffer = ctx.data.read().await.get::().cloned().unwrap(); + lock.insert( + (guild_id, ctx.author().id), + CommandMacro { guild_id, name, description, commands: vec![] }, + ); + true + } + }; - 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) - }), + 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", ) - .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. + .color(*THEME_COLOR) + }) + }) + .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(()) +} + +/// 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 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 { @@ -396,15 +400,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 +466,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 +479,8 @@ pub fn show_macro_page(macros: &[CommandMacro], page: usize) -> CreateGenericRes pager.create_button_row(pages, comp); comp - }) + }); + + reply + */ } diff --git a/src/component_models/mod.rs b/src/component_models/mod.rs index bf16b2b..d15c0f2 100644 --- a/src/component_models/mod.rs +++ b/src/component_models/mod.rs @@ -3,9 +3,7 @@ pub(crate) mod pager; use std::io::Cursor; use chrono_tz::Tz; -use rmp_serde::Serializer; -use serde::{Deserialize, Serialize}; -use serenity::{ +use poise::serenity::{ builder::CreateEmbed, client::Context, model::{ @@ -14,18 +12,14 @@ use serenity::{ prelude::InteractionApplicationCommandCallbackDataFlags, }, }; +use rmp_serde::Serializer; +use serde::{Deserialize, Serialize}; use crate::{ - commands::{ - moderation_cmds::{max_macro_page, show_macro_page}, - reminder_cmds::{max_delete_page, show_delete_page}, - todo_cmds::{max_todo_page, show_todo_page}, - }, + self, component_models::pager::{DelPager, LookPager, MacroPager, Pager, TodoPager}, consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR}, - framework::CommandInvoke, models::{command_macro::CommandMacro, reminder::Reminder}, - SQLPool, }; #[derive(Deserialize, Serialize)] diff --git a/src/component_models/pager.rs b/src/component_models/pager.rs index 8ab83e2..e82ff58 100644 --- a/src/component_models/pager.rs +++ b/src/component_models/pager.rs @@ -1,8 +1,10 @@ // todo split pager out into a single struct use chrono_tz::Tz; +use poise::serenity::{ + builder::CreateComponents, model::interactions::message_component::ButtonStyle, +}; use serde::{Deserialize, Serialize}; use serde_repr::*; -use serenity::{builder::CreateComponents, model::interactions::message_component::ButtonStyle}; use crate::{component_models::ComponentDataModel, models::reminder::look_flags::LookFlags}; diff --git a/src/consts.rs b/src/consts.rs index cf9aa92..42c7fce 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -4,14 +4,16 @@ pub const MINUTE: u64 = 60; pub const EMBED_DESCRIPTION_MAX_LENGTH: usize = 4000; pub const SELECT_MAX_ENTRIES: usize = 25; +pub const MACRO_MAX_COMMANDS: usize = 5; + pub const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; const THEME_COLOR_FALLBACK: u32 = 0x8fb677; use std::{collections::HashSet, env, iter::FromIterator}; +use poise::serenity::model::prelude::AttachmentType; use regex::Regex; -use serenity::http::AttachmentType; lazy_static! { pub static ref DEFAULT_AVATAR: AttachmentType<'static> = ( diff --git a/src/event_handlers.rs b/src/event_handlers.rs new file mode 100644 index 0000000..8a6b530 --- /dev/null +++ b/src/event_handlers.rs @@ -0,0 +1,83 @@ +use std::{collections::HashMap, env}; + +use poise::serenity::{client::Context, model::interactions::Interaction, utils::shard_id}; + +use crate::{Data, Error}; + +pub async fn listener(ctx: &Context, event: &poise::Event<'_>, data: &Data) -> Result<(), Error> { + match event { + poise::Event::ChannelDelete { channel } => { + sqlx::query!( + " +DELETE FROM channels WHERE channel = ? + ", + 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(); + + sqlx::query!("INSERT INTO guilds (guild) VALUES (?)", guild_id) + .execute(&data.database) + .await + .unwrap(); + + 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(), + ) + .header("Authorization", token) + .json(&hm) + .send() + .await; + + if let Err(res) = response { + println!("DiscordBots Response: {:?}", res); + } + } + } + } + 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; + } + _ => {} + }, + _ => {} + } + + Ok(()) +} diff --git a/src/framework.rs b/src/framework.rs deleted file mode 100644 index aaf9a28..0000000 --- a/src/framework.rs +++ /dev/null @@ -1,692 +0,0 @@ -// todo move framework to its own module, split out permission checks - -use std::{ - collections::{HashMap, HashSet}, - hash::{Hash, Hasher}, - sync::Arc, -}; - -use log::info; -use serde::{Deserialize, Serialize}; -use serenity::{ - builder::{CreateApplicationCommands, CreateComponents, CreateEmbed}, - cache::Cache, - client::Context, - futures::prelude::future::BoxFuture, - http::Http, - model::{ - guild::Guild, - id::{ChannelId, GuildId, RoleId, UserId}, - interactions::{ - application_command::{ - ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType, - }, - message_component::MessageComponentInteraction, - InteractionApplicationCommandCallbackDataFlags, InteractionResponseType, - }, - prelude::application_command::ApplicationCommandInteractionDataOption, - }, - prelude::TypeMapKey, - Result as SerenityResult, -}; - -use crate::SQLPool; - -pub struct CreateGenericResponse { - content: String, - embed: Option, - components: Option, - flags: InteractionApplicationCommandCallbackDataFlags, -} - -impl CreateGenericResponse { - pub fn new() -> Self { - Self { - content: "".to_string(), - embed: None, - components: None, - flags: InteractionApplicationCommandCallbackDataFlags::empty(), - } - } - - pub fn ephemeral(mut self) -> Self { - self.flags.insert(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL); - - self - } - - pub fn content(mut self, content: D) -> Self { - self.content = content.to_string(); - - self - } - - pub fn embed &mut CreateEmbed>(mut self, f: F) -> Self { - let mut embed = CreateEmbed::default(); - f(&mut embed); - - self.embed = Some(embed); - self - } - - pub fn components &mut CreateComponents>( - mut self, - f: F, - ) -> Self { - let mut components = CreateComponents::default(); - f(&mut components); - - self.components = Some(components); - self - } -} - -#[derive(Clone)] -enum InvokeModel { - Slash(ApplicationCommandInteraction), - Component(MessageComponentInteraction), -} - -#[derive(Clone)] -pub struct CommandInvoke { - model: InvokeModel, - already_responded: bool, - deferred: bool, -} - -impl CommandInvoke { - pub fn component(component: MessageComponentInteraction) -> Self { - Self { model: InvokeModel::Component(component), already_responded: false, deferred: false } - } - - fn slash(interaction: ApplicationCommandInteraction) -> Self { - Self { model: InvokeModel::Slash(interaction), already_responded: false, deferred: false } - } - - pub async fn defer(&mut self, http: impl AsRef) { - if !self.deferred { - match &self.model { - InvokeModel::Slash(i) => { - i.create_interaction_response(http, |r| { - r.kind(InteractionResponseType::DeferredChannelMessageWithSource) - }) - .await - .unwrap(); - - self.deferred = true; - } - InvokeModel::Component(i) => { - i.create_interaction_response(http, |r| { - r.kind(InteractionResponseType::DeferredChannelMessageWithSource) - }) - .await - .unwrap(); - - self.deferred = true; - } - } - } - } - - pub fn channel_id(&self) -> ChannelId { - match &self.model { - InvokeModel::Slash(i) => i.channel_id, - InvokeModel::Component(i) => i.channel_id, - } - } - - pub fn guild_id(&self) -> Option { - match &self.model { - InvokeModel::Slash(i) => i.guild_id, - InvokeModel::Component(i) => i.guild_id, - } - } - - pub fn guild(&self, cache: impl AsRef) -> Option { - self.guild_id().map(|id| id.to_guild_cached(cache)).flatten() - } - - pub fn author_id(&self) -> UserId { - match &self.model { - InvokeModel::Slash(i) => i.user.id, - InvokeModel::Component(i) => i.user.id, - } - } - - pub async fn respond( - &mut self, - http: impl AsRef, - generic_response: CreateGenericResponse, - ) -> SerenityResult<()> { - match &self.model { - InvokeModel::Slash(i) => { - if self.already_responded { - i.create_followup_message(http, |d| { - d.allowed_mentions(|m| m.empty_parse()); - d.content(generic_response.content); - - if let Some(embed) = generic_response.embed { - d.add_embed(embed); - } - - if let Some(components) = generic_response.components { - d.components(|c| { - *c = components; - c - }); - } - - d - }) - .await - .map(|_| ()) - } else if self.deferred { - i.edit_original_interaction_response(http, |d| { - d.allowed_mentions(|m| m.empty_parse()); - d.content(generic_response.content); - - if let Some(embed) = generic_response.embed { - d.add_embed(embed); - } - - if let Some(components) = generic_response.components { - d.components(|c| { - *c = components; - c - }); - } - - d - }) - .await - .map(|_| ()) - } else { - i.create_interaction_response(http, |r| { - r.kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|d| { - d.allowed_mentions(|m| m.empty_parse()); - d.content(generic_response.content); - - if let Some(embed) = generic_response.embed { - d.add_embed(embed); - } - - if let Some(components) = generic_response.components { - d.components(|c| { - *c = components; - c - }); - } - - d - }) - }) - .await - .map(|_| ()) - } - } - InvokeModel::Component(i) => i - .create_interaction_response(http, |r| { - r.kind(InteractionResponseType::UpdateMessage).interaction_response_data(|d| { - d.allowed_mentions(|m| m.empty_parse()); - d.content(generic_response.content); - - if let Some(embed) = generic_response.embed { - d.add_embed(embed); - } - - if let Some(components) = generic_response.components { - d.components(|c| { - *c = components; - c - }); - } - - d - }) - }) - .await - .map(|_| ()), - }?; - - self.already_responded = true; - - Ok(()) - } -} - -#[derive(Debug)] -pub struct Arg { - pub name: &'static str, - pub description: &'static str, - pub kind: ApplicationCommandOptionType, - pub required: bool, - pub options: &'static [&'static Self], -} - -#[derive(Serialize, Deserialize, Clone)] -pub enum OptionValue { - String(String), - Integer(i64), - Boolean(bool), - User(UserId), - Channel(ChannelId), - Role(RoleId), - Mentionable(u64), - Number(f64), -} - -impl OptionValue { - pub fn as_i64(&self) -> Option { - match self { - OptionValue::Integer(i) => Some(*i), - _ => None, - } - } - - pub fn as_bool(&self) -> Option { - match self { - OptionValue::Boolean(b) => Some(*b), - _ => None, - } - } - - pub fn as_channel_id(&self) -> Option { - match self { - OptionValue::Channel(c) => Some(*c), - _ => None, - } - } - - pub fn to_string(&self) -> String { - match self { - OptionValue::String(s) => s.to_string(), - OptionValue::Integer(i) => i.to_string(), - OptionValue::Boolean(b) => b.to_string(), - OptionValue::User(u) => u.to_string(), - OptionValue::Channel(c) => c.to_string(), - OptionValue::Role(r) => r.to_string(), - OptionValue::Mentionable(m) => m.to_string(), - OptionValue::Number(n) => n.to_string(), - } - } -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct CommandOptions { - pub command: String, - pub subcommand: Option, - pub subcommand_group: Option, - pub options: HashMap, -} - -impl CommandOptions { - pub fn get(&self, key: &str) -> Option<&OptionValue> { - self.options.get(key) - } -} - -impl CommandOptions { - fn new(command: &'static Command) -> Self { - Self { - command: command.names[0].to_string(), - subcommand: None, - subcommand_group: None, - options: Default::default(), - } - } - - fn populate(mut self, interaction: &ApplicationCommandInteraction) -> Self { - fn match_option( - option: ApplicationCommandInteractionDataOption, - cmd_opts: &mut CommandOptions, - ) { - match option.kind { - ApplicationCommandOptionType::SubCommand => { - cmd_opts.subcommand = Some(option.name); - - for opt in option.options { - match_option(opt, cmd_opts); - } - } - ApplicationCommandOptionType::SubCommandGroup => { - cmd_opts.subcommand_group = Some(option.name); - - for opt in option.options { - match_option(opt, cmd_opts); - } - } - ApplicationCommandOptionType::String => { - cmd_opts.options.insert( - option.name, - OptionValue::String(option.value.unwrap().as_str().unwrap().to_string()), - ); - } - ApplicationCommandOptionType::Integer => { - cmd_opts.options.insert( - option.name, - OptionValue::Integer(option.value.map(|m| m.as_i64()).flatten().unwrap()), - ); - } - ApplicationCommandOptionType::Boolean => { - cmd_opts.options.insert( - option.name, - OptionValue::Boolean(option.value.map(|m| m.as_bool()).flatten().unwrap()), - ); - } - ApplicationCommandOptionType::User => { - cmd_opts.options.insert( - option.name, - OptionValue::User(UserId( - option - .value - .map(|m| m.as_str().map(|s| s.parse::().ok())) - .flatten() - .flatten() - .unwrap(), - )), - ); - } - ApplicationCommandOptionType::Channel => { - cmd_opts.options.insert( - option.name, - OptionValue::Channel(ChannelId( - option - .value - .map(|m| m.as_str().map(|s| s.parse::().ok())) - .flatten() - .flatten() - .unwrap(), - )), - ); - } - ApplicationCommandOptionType::Role => { - cmd_opts.options.insert( - option.name, - OptionValue::Role(RoleId( - option - .value - .map(|m| m.as_str().map(|s| s.parse::().ok())) - .flatten() - .flatten() - .unwrap(), - )), - ); - } - ApplicationCommandOptionType::Mentionable => { - cmd_opts.options.insert( - option.name, - OptionValue::Mentionable( - option.value.map(|m| m.as_u64()).flatten().unwrap(), - ), - ); - } - ApplicationCommandOptionType::Number => { - cmd_opts.options.insert( - option.name, - OptionValue::Number(option.value.map(|m| m.as_f64()).flatten().unwrap()), - ); - } - _ => {} - } - } - - for option in &interaction.data.options { - match_option(option.clone(), &mut self) - } - - self - } -} - -pub enum HookResult { - Continue, - Halt, -} - -type SlashCommandFn = - for<'fut> fn(&'fut Context, &'fut mut CommandInvoke, CommandOptions) -> BoxFuture<'fut, ()>; - -type MultiCommandFn = for<'fut> fn(&'fut Context, &'fut mut CommandInvoke) -> BoxFuture<'fut, ()>; - -pub type HookFn = for<'fut> fn( - &'fut Context, - &'fut mut CommandInvoke, - &'fut CommandOptions, -) -> BoxFuture<'fut, HookResult>; - -pub enum CommandFnType { - Slash(SlashCommandFn), - Multi(MultiCommandFn), -} - -pub struct Hook { - pub fun: HookFn, - pub uuid: u128, -} - -impl PartialEq for Hook { - fn eq(&self, other: &Self) -> bool { - self.uuid == other.uuid - } -} - -pub struct Command { - pub fun: CommandFnType, - - pub names: &'static [&'static str], - - pub desc: &'static str, - pub examples: &'static [&'static str], - pub group: &'static str, - - pub args: &'static [&'static Arg], - - pub can_blacklist: bool, - pub supports_dm: bool, - - pub hooks: &'static [&'static Hook], -} - -impl Hash for Command { - fn hash(&self, state: &mut H) { - self.names[0].hash(state) - } -} - -impl PartialEq for Command { - fn eq(&self, other: &Self) -> bool { - self.names[0] == other.names[0] - } -} - -impl Eq for Command {} - -pub struct RegexFramework { - pub commands_map: HashMap, - pub commands: HashSet<&'static Command>, - ignore_bots: bool, - dm_enabled: bool, - debug_guild: Option, - hooks: Vec<&'static Hook>, -} - -impl TypeMapKey for RegexFramework { - type Value = Arc; -} - -impl RegexFramework { - pub fn new() -> Self { - Self { - commands_map: HashMap::new(), - commands: HashSet::new(), - ignore_bots: true, - dm_enabled: true, - debug_guild: None, - hooks: vec![], - } - } - - pub fn ignore_bots(mut self, ignore_bots: bool) -> Self { - self.ignore_bots = ignore_bots; - - self - } - - pub fn dm_enabled(mut self, dm_enabled: bool) -> Self { - self.dm_enabled = dm_enabled; - - self - } - - pub fn add_hook(mut self, fun: &'static Hook) -> Self { - self.hooks.push(fun); - - self - } - - pub fn add_command(mut self, command: &'static Command) -> Self { - self.commands.insert(command); - - for name in command.names { - self.commands_map.insert(name.to_string(), command); - } - - self - } - - pub fn debug_guild(mut self, guild_id: Option) -> Self { - self.debug_guild = guild_id; - - self - } - - fn _populate_commands<'a>( - &self, - commands: &'a mut CreateApplicationCommands, - ) -> &'a mut CreateApplicationCommands { - for command in &self.commands { - commands.create_application_command(|c| { - c.name(command.names[0]).description(command.desc); - - for arg in command.args { - c.create_option(|o| { - o.name(arg.name) - .description(arg.description) - .kind(arg.kind) - .required(arg.required); - - for option in arg.options { - o.create_sub_option(|s| { - s.name(option.name) - .description(option.description) - .kind(option.kind) - .required(option.required); - - for sub_option in option.options { - s.create_sub_option(|ss| { - ss.name(sub_option.name) - .description(sub_option.description) - .kind(sub_option.kind) - .required(sub_option.required) - }); - } - - s - }); - } - - o - }); - } - - c - }); - } - - commands - } - - pub async fn build_slash(&self, http: impl AsRef) { - info!("Building slash commands..."); - - match self.debug_guild { - None => { - ApplicationCommand::set_global_application_commands(&http, |c| { - self._populate_commands(c) - }) - .await - .unwrap(); - } - Some(debug_guild) => { - debug_guild - .set_application_commands(&http, |c| self._populate_commands(c)) - .await - .unwrap(); - } - } - - info!("Slash commands built!"); - } - - pub async fn execute(&self, ctx: Context, interaction: ApplicationCommandInteraction) { - { - if let Some(guild_id) = interaction.guild_id { - let pool = ctx.data.read().await.get::().cloned().unwrap(); - let _ = sqlx::query!("INSERT IGNORE INTO guilds (guild) VALUES (?)", guild_id.0) - .execute(&pool) - .await; - } - } - - let command = { - self.commands_map - .get(&interaction.data.name) - .expect(&format!("Received invalid command: {}", interaction.data.name)) - }; - - let args = CommandOptions::new(command).populate(&interaction); - let mut command_invoke = CommandInvoke::slash(interaction); - - for hook in command.hooks { - match (hook.fun)(&ctx, &mut command_invoke, &args).await { - HookResult::Continue => {} - HookResult::Halt => { - return; - } - } - } - - for hook in &self.hooks { - match (hook.fun)(&ctx, &mut command_invoke, &args).await { - HookResult::Continue => {} - HookResult::Halt => { - return; - } - } - } - - match command.fun { - CommandFnType::Slash(t) => t(&ctx, &mut command_invoke, args).await, - CommandFnType::Multi(m) => m(&ctx, &mut command_invoke).await, - } - } - - pub async fn run_command_from_options( - &self, - ctx: &Context, - command_invoke: &mut CommandInvoke, - command_options: CommandOptions, - ) { - let command = { - self.commands_map - .get(&command_options.command) - .expect(&format!("Received invalid command: {}", command_options.command)) - }; - - match command.fun { - CommandFnType::Slash(t) => t(&ctx, command_invoke, command_options).await, - CommandFnType::Multi(m) => m(&ctx, command_invoke).await, - } - } -} diff --git a/src/hooks.rs b/src/hooks.rs index fae9bcf..0fa08a5 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -1,91 +1,77 @@ -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, models::command_macro::CommandOptions, 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(false).content( + "5 commands already recorded. Please use `/macro finish` to end recording.", + ) + }) + .await; + } else { + let mut command_options = CommandOptions::new(&ctx.command().name); + command_options.populate(&interaction); + + command_macro.commands.push(command_options); + + let _ = ctx + .send(|m| m.ephemeral(false).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 +82,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 +97,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/main.rs b/src/main.rs index 2a23a58..1592879 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,179 +3,45 @@ extern crate lazy_static; mod commands; -mod component_models; +// mod component_models; mod consts; -mod framework; +mod event_handlers; mod hooks; mod models; mod time_parser; +mod utils; -use std::{collections::HashMap, env, sync::Arc}; +use std::{collections::HashMap, env}; use chrono_tz::Tz; use dotenv::dotenv; -use log::info; -use serenity::{ - async_trait, - client::{bridge::gateway::GatewayIntents, Client}, - http::{client::Http, CacheHttp}, - model::{ - channel::GuildChannel, - gateway::{Activity, Ready}, - guild::{Guild, GuildUnavailable}, - id::{GuildId, UserId}, - interactions::Interaction, - }, - prelude::{Context, EventHandler, TypeMapKey}, - utils::shard_id, +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, - consts::{CNC_GUILD, SUBSCRIPTION_ROLES, THEME_COLOR}, - framework::RegexFramework, + commands::{info_cmds, moderation_cmds}, + consts::THEME_COLOR, + 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; +pub struct Data { + database: Pool, + http: reqwest::Client, + recording_macros: RwLock>, + popular_timezones: Vec, } -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; - -#[async_trait] -impl EventHandler for Handler { - 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!( - " -DELETE FROM channels WHERE channel = ? - ", - channel.id.as_u64() - ) - .execute(&pool) - .await - .unwrap(); - } - - 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() - .await - .get::() - .cloned() - .expect("Could not get ReqwestClient from data"); - - let response = client - .post( - format!( - "https://top.gg/api/bots/{}/stats", - ctx.cache.current_user_id().as_u64() - ) - .as_str(), - ) - .header("Authorization", token) - .json(&hm) - .send() - .await; - - if let Err(res) = response { - println!("DiscordBots Response: {:?}", res); - } - } - } - } - - async fn guild_delete(&self, ctx: Context, incomplete: GuildUnavailable, _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; - } - Interaction::MessageComponent(component) => { - let component_model = ComponentDataModel::from_custom_id(&component.data.custom_id); - component_model.act(&ctx, component).await; - } - _ => {} - } - } -} +type Error = Box; +type Context<'a> = poise::Context<'a, Data, Error>; #[tokio::main] async fn main() -> Result<(), Box> { @@ -183,139 +49,75 @@ 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 http = Http::new_with_token(&token); + 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 application_id = http.get_current_application_info().await?.id; + let database = + Pool::connect(&env::var("DATABASE_URL").expect("No database URL provided")).await.unwrap(); - let dm_enabled = env::var("DM_ENABLED").map_or(true, |var| var == "1"); + 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 = 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); + 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 framework_arc = Arc::new(framework); + register_application_commands( + ctx, + framework, + env::var("DEBUG_GUILD") + .map(|inner| GuildId(inner.parse().expect("DEBUG_GUILD not valid"))) + .ok(), + ) + .await + .unwrap(); - let mut client = Client::builder(&token) - .intents(GatewayIntents::GUILDS) - .application_id(application_id.0) - .event_handler(Handler) - .await - .expect("Error occurred creating client"); - - { - 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; - - if let Ok((Some(lower), Some(upper))) = env::var("SHARD_RANGE").map(|sr| { - let mut split = - sr.split(',').map(|val| val.parse::().expect("SHARD_RANGE not an integer")); - - (split.next(), split.next()) - }) { - let total_shards = env::var("SHARD_COUNT") - .map(|shard_count| shard_count.parse::().ok()) - .ok() - .flatten() - .expect("No SHARD_COUNT provided, but SHARD_RANGE was provided"); - - assert!(lower < upper, "SHARD_RANGE lower limit is not less than the upper limit"); - - info!("Starting client fragment with shards {}-{}/{}", lower, upper, total_shards); - - client.start_shard_range([lower, upper], total_shards).await?; - } else if let Ok(total_shards) = env::var("SHARD_COUNT") - .map(|shard_count| shard_count.parse::().expect("SHARD_COUNT not an integer")) - { - info!("Starting client with {} shards", total_shards); - - client.start_shards(total_shards).await?; - } else { - info!("Starting client as autosharded"); - - client.start_autosharded().await?; - } + Ok(Data { + http: reqwest::Client::new(), + database, + popular_timezones, + recording_macros: Default::default(), + }) + }) + }) + .options(options) + .client_settings(move |client_builder| client_builder.intents(GatewayIntents::GUILDS)) + .run_autosharded() + .await?; Ok(()) } - -pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into) -> bool { - if let Some(subscription_guild) = *CNC_GUILD { - let guild_member = GuildId(subscription_guild).member(cache_http, user_id).await; - - if let Ok(member) = guild_member { - for role in member.roles { - if SUBSCRIPTION_ROLES.contains(role.as_u64()) { - return true; - } - } - } - - false - } else { - true - } -} - -pub async fn check_guild_subscription( - cache_http: impl CacheHttp, - guild_id: impl Into, -) -> bool { - if let Some(guild) = cache_http.cache().unwrap().guild(guild_id) { - let owner = guild.owner_id; - - check_subscription(&cache_http, owner).await - } else { - false - } -} 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..cc1565a 100644 --- a/src/models/command_macro.rs +++ b/src/models/command_macro.rs @@ -1,6 +1,16 @@ -use serenity::{client::Context, model::id::GuildId}; +use std::collections::HashMap; -use crate::{framework::CommandOptions, SQLPool}; +use poise::serenity::model::{ + id::{ChannelId, GuildId, RoleId, UserId}, + interactions::application_command::{ + ApplicationCommandInteraction, ApplicationCommandInteractionDataOption, + ApplicationCommandOptionType, + }, +}; +use serde::{Deserialize, Serialize}; +use sqlx::Executor; + +use crate::Database; pub struct CommandMacro { pub guild_id: GuildId, @@ -10,15 +20,17 @@ pub struct CommandMacro { } impl CommandMacro { - pub async fn from_guild(ctx: &Context, guild_id: impl Into) -> Vec { - let pool = ctx.data.read().await.get::().cloned().unwrap(); + pub async fn from_guild( + db_pool: impl Executor<'_, Database = Database>, + guild_id: impl Into, + ) -> Vec { 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) + .fetch_all(db_pool) .await .unwrap() .iter() @@ -31,3 +43,170 @@ impl CommandMacro { .collect::>() } } + +#[derive(Serialize, Deserialize, Clone)] +pub enum OptionValue { + String(String), + Integer(i64), + Boolean(bool), + User(UserId), + Channel(ChannelId), + Role(RoleId), + Mentionable(u64), + Number(f64), +} + +impl OptionValue { + pub fn as_i64(&self) -> Option { + match self { + OptionValue::Integer(i) => Some(*i), + _ => None, + } + } + + pub fn as_bool(&self) -> Option { + match self { + OptionValue::Boolean(b) => Some(*b), + _ => None, + } + } + + pub fn as_channel_id(&self) -> Option { + match self { + OptionValue::Channel(c) => Some(*c), + _ => None, + } + } + + pub fn to_string(&self) -> String { + match self { + OptionValue::String(s) => s.to_string(), + OptionValue::Integer(i) => i.to_string(), + OptionValue::Boolean(b) => b.to_string(), + OptionValue::User(u) => u.to_string(), + OptionValue::Channel(c) => c.to_string(), + OptionValue::Role(r) => r.to_string(), + OptionValue::Mentionable(m) => m.to_string(), + OptionValue::Number(n) => n.to_string(), + } + } +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct CommandOptions { + pub command: String, + pub subcommand: Option, + pub subcommand_group: Option, + pub options: HashMap, +} + +impl CommandOptions { + pub fn new(command: impl ToString) -> Self { + Self { + command: command.to_string(), + subcommand: None, + subcommand_group: None, + options: Default::default(), + } + } + + pub fn populate(&mut self, interaction: &ApplicationCommandInteraction) { + fn match_option( + option: ApplicationCommandInteractionDataOption, + cmd_opts: &mut CommandOptions, + ) { + match option.kind { + ApplicationCommandOptionType::SubCommand => { + cmd_opts.subcommand = Some(option.name); + + for opt in option.options { + match_option(opt, cmd_opts); + } + } + ApplicationCommandOptionType::SubCommandGroup => { + cmd_opts.subcommand_group = Some(option.name); + + for opt in option.options { + match_option(opt, cmd_opts); + } + } + ApplicationCommandOptionType::String => { + cmd_opts.options.insert( + option.name, + OptionValue::String(option.value.unwrap().as_str().unwrap().to_string()), + ); + } + ApplicationCommandOptionType::Integer => { + cmd_opts.options.insert( + option.name, + OptionValue::Integer(option.value.map(|m| m.as_i64()).flatten().unwrap()), + ); + } + ApplicationCommandOptionType::Boolean => { + cmd_opts.options.insert( + option.name, + OptionValue::Boolean(option.value.map(|m| m.as_bool()).flatten().unwrap()), + ); + } + ApplicationCommandOptionType::User => { + cmd_opts.options.insert( + option.name, + OptionValue::User(UserId( + option + .value + .map(|m| m.as_str().map(|s| s.parse::().ok())) + .flatten() + .flatten() + .unwrap(), + )), + ); + } + ApplicationCommandOptionType::Channel => { + cmd_opts.options.insert( + option.name, + OptionValue::Channel(ChannelId( + option + .value + .map(|m| m.as_str().map(|s| s.parse::().ok())) + .flatten() + .flatten() + .unwrap(), + )), + ); + } + ApplicationCommandOptionType::Role => { + cmd_opts.options.insert( + option.name, + OptionValue::Role(RoleId( + option + .value + .map(|m| m.as_str().map(|s| s.parse::().ok())) + .flatten() + .flatten() + .unwrap(), + )), + ); + } + ApplicationCommandOptionType::Mentionable => { + cmd_opts.options.insert( + option.name, + OptionValue::Mentionable( + option.value.map(|m| m.as_u64()).flatten().unwrap(), + ), + ); + } + ApplicationCommandOptionType::Number => { + cmd_opts.options.insert( + option.name, + OptionValue::Number(option.value.map(|m| m.as_f64()).flatten().unwrap()), + ); + } + _ => {} + } + } + + for option in &interaction.data.options { + match_option(option.clone(), self) + } + } +} 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 f258f37..b3d8ffa 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,14 +14,13 @@ use serenity::{ use sqlx::MySqlPool; use crate::{ - consts, - consts::{MAX_TIME, MIN_INTERVAL}, + consts::{DEFAULT_AVATAR, MAX_TIME, MIN_INTERVAL}, models::{ channel_data::ChannelData, reminder::{content::Content, errors::ReminderError, helper::generate_uid, Reminder}, user_data::UserData, }, - SQLPool, + Context, }; async fn create_webhook( @@ -30,7 +28,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)] @@ -140,12 +138,12 @@ pub struct MultiReminderBuilder<'a> { expires: Option, content: Content, set_by: Option, - ctx: &'a Context, + ctx: &'a Context<'a>, guild_id: Option, } impl<'a> MultiReminderBuilder<'a> { - pub fn new(ctx: &'a Context, guild_id: Option) -> Self { + pub fn new(ctx: &'a Context<'a>, guild_id: Option) -> Self { MultiReminderBuilder { scopes: vec![], utc_time: Utc::now().naive_utc(), @@ -199,7 +197,7 @@ impl<'a> MultiReminderBuilder<'a> { } pub async fn build(self) -> (HashSet, HashSet) { - let pool = self.ctx.data.read().await.get::().cloned().unwrap(); + let pool = self.ctx.data().database.clone(); let mut errors = HashSet::new(); @@ -213,12 +211,13 @@ 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(), &pool) + .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) @@ -231,7 +230,8 @@ 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 { @@ -243,7 +243,12 @@ impl<'a> MultiReminderBuilder<'a> { 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 = 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 573e8ca..8abfdd6 100644 --- a/src/models/reminder/mod.rs +++ b/src/models/reminder/mod.rs @@ -6,18 +6,15 @@ 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, MySqlPool}; use crate::{ models::reminder::{ helper::longhand_displacement, look_flags::{LookFlags, TimeDisplayType}, }, - SQLPool, + Context, Database, }; #[derive(Debug, Clone)] @@ -71,12 +68,10 @@ WHERE } pub async fn from_channel>( - ctx: &Context, + db_pool: impl Executor<'_, Database = Database>, 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 +108,21 @@ ORDER BY channel_id.as_u64(), enabled, ) - .fetch_all(&pool) + .fetch_all(db_pool) .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 { + // todo: see if this can be moved to just extract from the context + let pool = ctx.data().database.clone(); 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 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 new file mode 100644 index 0000000..e11f47c --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,67 @@ +use poise::serenity::{ + builder::CreateApplicationCommands, + http::CacheHttp, + model::id::{GuildId, UserId}, +}; + +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 { + let guild_member = GuildId(subscription_guild).member(cache_http, user_id).await; + + if let Ok(member) = guild_member { + for role in member.roles { + if SUBSCRIPTION_ROLES.contains(role.as_u64()) { + return true; + } + } + } + + false + } else { + true + } +} + +pub async fn check_guild_subscription( + cache_http: impl CacheHttp, + guild_id: impl Into, +) -> bool { + if let Some(guild) = cache_http.cache().unwrap().guild(guild_id) { + let owner = guild.owner_id; + + check_subscription(&cache_http, owner).await + } else { + false + } +}