From ba7b82a6de3624ad59b587fa52ee784c3f2c9cf8 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Tue, 9 Aug 2022 18:08:16 +0200 Subject: [PATCH] feat: migrate to command framework --- .vscode/launch.json | 5 +- .vscode/settings.json | 3 - Cargo.lock | 324 ++++++++++++++++++++++++-------- Cargo.toml | 9 +- configuration.example.json | 3 + configuration.schema.json | 14 +- src/commands/configuration.rs | 46 +++++ src/commands/mod.rs | 3 + src/commands/moderation.rs | 1 + src/commands/utils.rs | 1 + src/events/message_create.rs | 82 ++++++++ src/events/mod.rs | 94 ++++++++++ src/events/thread_create.rs | 29 +++ src/main.rs | 343 ++++++++++------------------------ src/model/application.rs | 12 +- src/utils.rs | 32 ++++ 16 files changed, 663 insertions(+), 338 deletions(-) delete mode 100644 .vscode/settings.json create mode 100644 src/commands/configuration.rs create mode 100644 src/commands/mod.rs create mode 100644 src/commands/moderation.rs create mode 100644 src/commands/utils.rs create mode 100644 src/events/message_create.rs create mode 100644 src/events/mod.rs create mode 100644 src/events/thread_create.rs create mode 100644 src/utils.rs diff --git a/.vscode/launch.json b/.vscode/launch.json index c436a31..c01771c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,4 +1,7 @@ { + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { @@ -39,4 +42,4 @@ "cwd": "${workspaceFolder}" } ] -} +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index e34fca1..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "conventionalCommits.scopes": ["clippy"] -} diff --git a/Cargo.lock b/Cargo.lock index 0a89db2..47878b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,9 +28,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" dependencies = [ "proc-macro2", "quote", @@ -94,9 +94,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" [[package]] name = "cc" @@ -112,14 +112,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "6127248204b9aba09a362f6c930ef6a78f2c1b2215f8a7b398c06e1083f17af0" dependencies = [ - "libc", + "js-sys", "num-integer", "num-traits", + "serde", "time 0.1.44", + "wasm-bindgen", "winapi", ] @@ -143,14 +145,79 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5999502d32b9c48d492abe66392408144895020ec4709e549e840799f3bb74c0" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] +[[package]] +name = "darling" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dashmap" +version = "5.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f" +dependencies = [ + "cfg-if", + "hashbrown", + "lock_api", + "parking_lot_core", + "serde", +] + +[[package]] +name = "decancer" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b136f741547a1813c4cbc9b5b214770b6812207191feb065ac238beeea882fc" + +[[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.10.3" @@ -289,9 +356,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", "version_check", @@ -329,9 +396,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hermit-abi" @@ -413,6 +480,12 @@ dependencies = [ "tokio-rustls", ] +[[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" @@ -442,15 +515,15 @@ checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" [[package]] name = "itoa" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "js-sys" -version = "0.3.58" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" dependencies = [ "wasm-bindgen", ] @@ -463,9 +536,19 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" +checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] [[package]] name = "log" @@ -578,6 +661,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -596,6 +702,37 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "poise" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6c01d22dcda434b0dfe956c60f6ac9b0352c4c2f4af852afb3155a971cd306d" +dependencies = [ + "async-trait", + "derivative", + "futures-core", + "futures-util", + "log", + "once_cell", + "parking_lot", + "poise_macros", + "regex", + "serenity", + "tokio", +] + +[[package]] +name = "poise_macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff861b6a52ec47bc54eb17424c025feeb040e82836036276c25dda045a8a0c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -604,18 +741,18 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" -version = "1.0.40" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" dependencies = [ "proc-macro2", ] @@ -652,9 +789,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", ] @@ -730,16 +867,17 @@ dependencies = [ [[package]] name = "revanced-discord-bot" -version = "0.1.0" +version = "1.1.0" dependencies = [ "chrono", + "decancer", "dirs", "dotenv", + "poise", "regex", "serde", "serde_json", "serde_regex", - "serenity", "tokio", "tracing", "tracing-subscriber", @@ -774,18 +912,30 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9" +checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" dependencies = [ "base64", ] +[[package]] +name = "rustversion" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" + [[package]] name = "ryu" -version = "1.0.10" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + +[[package]] +name = "scopeguard" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "sct" @@ -799,9 +949,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.138" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1578c6245786b9d168c5447eeacfb96856573ca56c9d68fdcf394be134882a47" +checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2" dependencies = [ "serde_derive", ] @@ -818,9 +968,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.138" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "023e9b1467aef8a10fb88f25611870ada9800ef7e22afce356bb0d2387b6f27c" +checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e" dependencies = [ "proc-macro2", "quote", @@ -829,9 +979,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.82" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" +checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" dependencies = [ "itoa", "ryu", @@ -862,8 +1012,9 @@ dependencies = [ [[package]] name = "serenity" -version = "0.11.2" -source = "git+https://github.com/serenity-rs/serenity.git#24c02845a0e9e7bde38dacd84607de973ffec10f" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fd5e7b5858ad96e99d440138f34f5b98e1b959ebcd3a1036203b30e78eb788" dependencies = [ "async-trait", "async-tungstenite", @@ -871,16 +1022,20 @@ dependencies = [ "bitflags", "bytes", "cfg-if", + "chrono", + "dashmap", "flate2", "futures", "mime", "mime_guess", + "parking_lot", "percent-encoding", "reqwest", + "rustversion", "serde", "serde-value", "serde_json", - "time 0.3.11", + "time 0.3.12", "tokio", "tracing", "typemap_rev", @@ -909,9 +1064,12 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" @@ -935,11 +1093,17 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "syn" -version = "1.0.98" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" dependencies = [ "proc-macro2", "quote", @@ -948,18 +1112,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.31" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21" dependencies = [ "proc-macro2", "quote", @@ -988,11 +1152,12 @@ dependencies = [ [[package]] name = "time" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" +checksum = "74b7cc93fc23ba97fde84f7eea56c55d1ba183f495c6715defdfc7b9cb8c870f" dependencies = [ "itoa", + "js-sys", "libc", "num_threads", "serde", @@ -1015,10 +1180,11 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.19.2" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439" +checksum = "7a8325f63a7d4774dd041e363b2409ed1c5cbbd0f867795e661df066b2b0a581" dependencies = [ + "autocfg", "bytes", "libc", "memchr", @@ -1075,9 +1241,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" dependencies = [ "cfg-if", "log", @@ -1099,9 +1265,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" +checksum = "5aeea4303076558a00714b823f9ad67d58a3bbda1df83d8827d21193156e22f7" dependencies = [ "once_cell", "valuable", @@ -1120,9 +1286,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59" +checksum = "60db860322da191b40952ad9affe65ea23e7dd6a5c442c2c42865810c6ab8e6b" dependencies = [ "ansi_term", "sharded-slab", @@ -1140,9 +1306,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tungstenite" -version = "0.17.2" +version = "0.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" +checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" dependencies = [ "base64", "byteorder", @@ -1188,9 +1354,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" +checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" [[package]] name = "unicode-normalization" @@ -1262,9 +1428,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.81" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1272,13 +1438,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.81" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", "syn", @@ -1287,9 +1453,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f" +checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad" dependencies = [ "cfg-if", "js-sys", @@ -1299,9 +1465,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.81" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1309,9 +1475,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.81" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" dependencies = [ "proc-macro2", "quote", @@ -1322,15 +1488,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.81" +version = "0.2.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" +checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" [[package]] name = "web-sys" -version = "0.3.58" +version = "0.3.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90" +checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -1348,9 +1514,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.3" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" +checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" dependencies = [ "webpki", ] diff --git a/Cargo.toml b/Cargo.toml index d3fdb96..425c050 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ keywords = ["ReVanced"] license = "GPL-3.0" name = "revanced-discord-bot" repository = "https://github.com/revanced/revanced-discord-bot" -version = "0.1.0" +version = "1.1.0" edition = "2021" [profile.release] @@ -17,14 +17,15 @@ codegen-units = 1 panic = "abort" [dependencies] -serde = { version = "1.0", features = ["derive"] } +poise = "0.3.0" +decancer = "1.4.1" # todo +tokio = { version = "1.20.1", features = ["rt-multi-thread"] } dotenv = "0.15" +serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -tokio = { version = "1.0", features = ["rt-multi-thread"] } regex = "1.0" serde_regex = "1.1" chrono = "0.4" -serenity = { git = "https://github.com/serenity-rs/serenity.git", default_features = false, features = ["client", "gateway", "rustls_backend", "model"] } dirs = "4.0.0" tracing = { version = "0.1", features = ["max_level_debug", "release_max_level_info"] } tracing-subscriber = "0.3" \ No newline at end of file diff --git a/configuration.example.json b/configuration.example.json index 9a042ae..581c4c4 100644 --- a/configuration.example.json +++ b/configuration.example.json @@ -1,5 +1,8 @@ { "$schema": "./configuration.schema.json", + "general": { + "embed_color": 0 + }, "administrators": { "roles": [0], "users": [0] diff --git a/configuration.schema.json b/configuration.schema.json index 6367541..e577d2b 100644 --- a/configuration.schema.json +++ b/configuration.schema.json @@ -5,6 +5,14 @@ "description": "The Revanced Discord bot configuration schema.", "type": "object", "properties": { + "general": { + "type": "object", + "properties": { + "embed_color": { + "$ref": "#/$defs/color" + } + } + }, "administrators": { "type": "object", "properties": { @@ -97,6 +105,10 @@ } }, "$defs": { + "color": { + "type": "integer", + "description": "The color of the embed." + }, "roles": { "type": "array", "items": { @@ -135,7 +147,7 @@ "description": "The description of the embed." }, "color": { - "type": "integer", + "$ref": "#/$defs/color", "description": "The color of the embed." }, "fields": { diff --git a/src/commands/configuration.rs b/src/commands/configuration.rs new file mode 100644 index 0000000..4689925 --- /dev/null +++ b/src/commands/configuration.rs @@ -0,0 +1,46 @@ +use crate::{utils::load_configuration, Context, Error}; +use tracing::debug; + +#[poise::command(slash_command, prefix_command)] +pub async fn reload(ctx: Context<'_>) -> Result<(), Error> { + // Update the configuration + let configuration = load_configuration(); + // Use the embed color from the updated configuration + let embed_color = configuration.general.embed_color; + // Also save the new configuration to the user data + *ctx.data().write().await = configuration; + + debug!("{:?} reloaded the configuration.", ctx.author().name); + + ctx.send(|f| { + f.ephemeral(true).embed(|f| { + f.description("Successfully reloaded configuration.") + .color(embed_color) + }) + }) + .await?; + + Ok(()) +} + +#[poise::command(slash_command, prefix_command)] +pub async fn stop(ctx: Context<'_>) -> Result<(), Error> { + debug!("{:?} stopped the bot.", ctx.author().name); + + let color = ctx.data().read().await.general.embed_color; + ctx.send(|f| { + f.ephemeral(true) + .embed(|f| f.description("Stopped the bot.").color(color)) + }) + .await?; + + ctx.discord().shard.shutdown_clean(); + + Ok(()) +} + +#[poise::command(prefix_command, slash_command, ephemeral = true)] +pub async fn register(ctx: Context<'_>) -> Result<(), Error> { + poise::builtins::register_application_commands_buttons(ctx).await?; + Ok(()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs new file mode 100644 index 0000000..4f527dc --- /dev/null +++ b/src/commands/mod.rs @@ -0,0 +1,3 @@ +pub mod configuration; +pub mod moderation; +pub mod utils; diff --git a/src/commands/moderation.rs b/src/commands/moderation.rs new file mode 100644 index 0000000..85d7407 --- /dev/null +++ b/src/commands/moderation.rs @@ -0,0 +1 @@ +// TODO mute/kick/ban/warn via database \ No newline at end of file diff --git a/src/commands/utils.rs b/src/commands/utils.rs new file mode 100644 index 0000000..6cbe5d8 --- /dev/null +++ b/src/commands/utils.rs @@ -0,0 +1 @@ +// TODO role assign buttons, embeds diff --git a/src/events/message_create.rs b/src/events/message_create.rs new file mode 100644 index 0000000..92166eb --- /dev/null +++ b/src/events/message_create.rs @@ -0,0 +1,82 @@ +use chrono::{DateTime, Duration, NaiveDateTime, Utc}; +use regex::Regex; +use tracing::debug; + +use super::*; + +fn contains_match(regex: &[Regex], text: &str) -> bool { + regex.iter().any(|r| r.is_match(text)) +} + +pub async fn message_create(ctx: &serenity::Context, new_message: &serenity::Message) { + debug!("Received message: {}", new_message.content); + if new_message.guild_id.is_none() || new_message.author.bot { + return; + } + + if let Some(message_response) = get_configuration_lock(&ctx) + .await + .read() + .await + .message_responses + .iter() + .find(|&response| { + // check if the message was sent in a channel that is included in the responder + response.includes.channels.iter().any(|&channel_id| channel_id == new_message.channel_id.0) + // check if the message was sent by a user that is not excluded from the responder + && !response.excludes.roles.iter().any(|&role_id| role_id == new_message.author.id.0) + // check if the message does not match any of the excludes + && !contains_match(&response.excludes.match_field, &new_message.content) + // check if the message matches any of the includes + && contains_match(&response.includes.match_field, &new_message.content) + }) + { + let min_age = message_response.condition.user.server_age; + + if min_age != 0 { + let joined_at = ctx + .http + .get_member(new_message.guild_id.unwrap().0, new_message.author.id.0) + .await + .unwrap() + .joined_at + .unwrap() + .unix_timestamp(); + + let must_joined_at = + DateTime::::from_utc(NaiveDateTime::from_timestamp(joined_at, 0), Utc); + let but_joined_at = Utc::now() - Duration::days(min_age); + + if must_joined_at <= but_joined_at { + return; + } + + new_message.channel_id + .send_message(&ctx.http, |m| { + m.reference_message(new_message); + match &message_response.response.embed { + Some(embed) => m.embed(|e| { + e.title(&embed.title) + .description(&embed.description) + .color(embed.color) + .fields(embed.fields.iter().map(|field| { + (field.name.clone(), field.value.clone(), field.inline) + })) + .footer(|f| { + f.text(&embed.footer.text); + f.icon_url(&embed.footer.icon_url) + }) + .thumbnail(&embed.thumbnail.url) + .image(&embed.image.url) + .author(|a| { + a.name(&embed.author.name).icon_url(&embed.author.icon_url) + }) + }), + None => m.content(message_response.response.message.as_ref().unwrap()), + } + }) + .await + .expect("Could not reply to message author."); + } + } +} diff --git a/src/events/mod.rs b/src/events/mod.rs new file mode 100644 index 0000000..dccb1a7 --- /dev/null +++ b/src/events/mod.rs @@ -0,0 +1,94 @@ +use poise::serenity_prelude::{self as serenity, Mutex, RwLock, ShardManager, UserId}; +use std::sync::Arc; + +use crate::{model::application::Configuration, Error}; + +mod message_create; +mod thread_create; + +// Share the lock reference between the threads in serenity framework +async fn get_configuration_lock(ctx: &serenity::Context) -> Arc> { + ctx.data + .read() + .await + .get::() + .expect("Expected Configuration in TypeMap.") + .clone() +} + +pub struct Handler { + options: poise::FrameworkOptions, + data: T, + bot_id: RwLock>, + shard_manager: RwLock>>>, +} + +// Custom handler to dispatch poise events +impl Handler { + pub fn new(options: poise::FrameworkOptions, data: T) -> Self { + Self { + options, + data, + shard_manager: RwLock::new(None), + bot_id: RwLock::new(None), + } + } + + pub async fn set_shard_manager(&self, shard_manager: Arc>) { + *self.shard_manager.write().await = Some(shard_manager); + } + + async fn dispatch_poise_event(&self, ctx: &serenity::Context, event: &poise::Event<'_>) { + let framework_data = poise::FrameworkContext { + bot_id: self.bot_id.read().await.unwrap(), + options: &self.options, + user_data: &self.data, + shard_manager: &(*self.shard_manager.read().await).clone().unwrap(), // Shard manager can be read between all poise events without locks + }; + poise::dispatch_event(framework_data, ctx, event).await; + } +} + +// Manually dispatch events from serenity to poise +#[serenity::async_trait] +impl serenity::EventHandler for Handler>> { + async fn ready(&self, _ctx: serenity::Context, ready: serenity::Ready) { + *self.bot_id.write().await = Some(ready.user.id); + } + + async fn message(&self, ctx: serenity::Context, new_message: serenity::Message) { + message_create::message_create(&ctx, &new_message).await; + + self.dispatch_poise_event(&ctx, &poise::Event::Message { new_message }) + .await; + } + + async fn interaction_create(&self, ctx: serenity::Context, interaction: serenity::Interaction) { + self.dispatch_poise_event(&ctx, &poise::Event::InteractionCreate { interaction }) + .await; + } + + async fn message_update( + &self, + ctx: serenity::Context, + old_if_available: Option, + new: Option, + event: serenity::MessageUpdateEvent, + ) { + self.dispatch_poise_event( + &ctx, + &poise::Event::MessageUpdate { + old_if_available, + new, + event, + }, + ) + .await; + } + + async fn thread_create(&self, ctx: serenity::Context, thread: serenity::GuildChannel) { + thread_create::thread_create(&ctx, &thread).await; + self.dispatch_poise_event(&ctx, &poise::Event::ThreadCreate { thread }) + .await; + } +} diff --git a/src/events/thread_create.rs b/src/events/thread_create.rs new file mode 100644 index 0000000..33d55bb --- /dev/null +++ b/src/events/thread_create.rs @@ -0,0 +1,29 @@ +use tracing::{debug, error}; + +use super::*; + +pub async fn thread_create(ctx: &serenity::Context, thread: &serenity::GuildChannel) { + if thread.member.is_some() { + debug!("Thread was joined. Block dispatch."); + return; + } + + debug!("Thread created: {:?}", thread); + + let configuration_lock = get_configuration_lock(&ctx).await; + let thread_introductions = &configuration_lock.read().await.thread_introductions; + + if let Some(introducer) = thread_introductions.iter().find(|introducer| { + introducer + .channels + .iter() + .any(|channel_id| *channel_id == thread.parent_id.unwrap().0) + }) { + if let Err(why) = thread + .say(&ctx.http, &introducer.response.message.as_ref().unwrap()) + .await + { + error!("Error sending message: {:?}", why); + } + } +} diff --git a/src/main.rs b/src/main.rs index 2672ad6..4da69aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,258 +1,107 @@ -use std::sync::Arc; -use std::{env, process}; +use crate::model::application::Configuration; +use std::{env, sync::Arc}; -use chrono::{DateTime, Duration, NaiveDateTime, Utc}; -use model::application::Configuration; -use regex::Regex; -use serenity::client::{Context, EventHandler}; -use serenity::model::channel::{GuildChannel, Message}; -use serenity::model::gateway::Ready; -use serenity::model::prelude::command::Command; -use serenity::model::prelude::interaction::{Interaction, InteractionResponseType, MessageFlags}; -use serenity::prelude::{GatewayIntents, RwLock, TypeMapKey}; -use serenity::{async_trait, Client}; -use tracing::{debug, error, info}; +use commands::configuration; +use events::Handler; +use poise::serenity_prelude::{self as serenity, RwLock}; +use utils::load_configuration; +mod commands; +mod events; mod logger; mod model; +mod utils; -struct BotConfiguration; +type Error = Box; +type Context<'a> = poise::Context<'a, Arc>, Error>; -impl TypeMapKey for BotConfiguration { - type Value = Arc>; -} - -pub struct Handler; - -async fn get_configuration_lock(ctx: &Context) -> Arc> { - ctx.data - .read() - .await - .get::() - .expect("Expected Configuration in TypeMap.") - .clone() -} - -fn contains_match(regex: &[Regex], text: &str) -> bool { - regex.iter().any(|r| r.is_match(text)) -} - -fn load_configuration() -> Configuration { - Configuration::load().expect("Failed to load configuration") -} - -#[async_trait] -impl EventHandler for Handler { - async fn interaction_create(&self, ctx: Context, interaction: Interaction) { - debug!("Created an interaction: {:?}", interaction); - - if let Interaction::ApplicationCommand(command) = interaction { - let configuration_lock = get_configuration_lock(&ctx).await; - let mut configuration = configuration_lock.write().await; - - let administrators = &configuration.administrators; - let member = command.member.as_ref().unwrap(); - let user_id = member.user.id.0; - let mut stop_command = false; - let mut permission_granted = false; - - // check if the user is an administrator - if administrators.users.iter().any(|&id| user_id == id) { - permission_granted = true - } - // check if the user has an administrating role - if !permission_granted - && administrators - .roles - .iter() - .any(|role_id| member.roles.iter().any(|member_role| member_role == role_id)) - { - permission_granted = true - } - - let content = if permission_granted { - match command.data.name.as_str() { - "reload" => { - debug!("{:?} reloaded the configuration.", command.user); - - let new_config = load_configuration(); - - configuration.administrators = new_config.administrators; - configuration.message_responses = new_config.message_responses; - configuration.thread_introductions = new_config.thread_introductions; - - "Successfully reloaded configuration.".to_string() - }, - "stop" => { - debug!("{:?} stopped the bot.", command.user); - stop_command = true; - "Stopped the bot.".to_string() - }, - _ => "Unknown command.".to_string(), - } - } else { - "You do not have permission to use this command.".to_string() - }; - - // send the response - if let Err(why) = command - .create_interaction_response(&ctx.http, |response| { - response - .kind(InteractionResponseType::ChannelMessageWithSource) - .interaction_response_data(|message| { - message.content(content).flags(MessageFlags::EPHEMERAL) - }) - }) - .await - { - error!("Cannot respond to slash command: {}", why); - } - - if stop_command { - process::exit(0); - } - } - } - - async fn message(&self, ctx: Context, msg: Message) { - debug!("Received message: {}", msg.content); - if msg.guild_id.is_none() || msg.author.bot { - return; - } - - if let Some(message_response) = - get_configuration_lock(&ctx).await.read().await.message_responses.iter().find( - |&response| { - // check if the message was sent in a channel that is included in the responder - response.includes.channels.iter().any(|&channel_id| channel_id == msg.channel_id.0) - // check if the message was sent by a user that is not excluded from the responder - && !response.excludes.roles.iter().any(|&role_id| role_id == msg.author.id.0) - // check if the message does not match any of the excludes - && !contains_match(&response.excludes.match_field, &msg.content) - // check if the message matches any of the includes - && contains_match(&response.includes.match_field, &msg.content) - }, - ) { - let min_age = message_response.condition.user.server_age; - - if min_age != 0 { - let joined_at = ctx - .http - .get_member(msg.guild_id.unwrap().0, msg.author.id.0) - .await - .unwrap() - .joined_at - .unwrap() - .unix_timestamp(); - - let must_joined_at = - DateTime::::from_utc(NaiveDateTime::from_timestamp(joined_at, 0), Utc); - let but_joined_at = Utc::now() - Duration::days(min_age); - - if must_joined_at <= but_joined_at { - return; - } - - msg.channel_id - .send_message(&ctx.http, |m| { - m.reference_message(&msg); - match &message_response.response.embed { - Some(embed) => m.embed(|e| { - e.title(&embed.title) - .description(&embed.description) - .color(embed.color) - .fields(embed.fields.iter().map(|field| { - (field.name.clone(), field.value.clone(), field.inline) - })) - .footer(|f| { - f.text(&embed.footer.text); - f.icon_url(&embed.footer.icon_url) - }) - .thumbnail(&embed.thumbnail.url) - .image(&embed.image.url) - .author(|a| { - a.name(&embed.author.name).icon_url(&embed.author.icon_url) - }) - }), - None => m.content(message_response.response.message.as_ref().unwrap()), - } - }) - .await - .expect("Could not reply to message author."); - } - } - } - - async fn thread_create(&self, ctx: Context, thread: GuildChannel) { - if thread.member.is_some() { - debug!("Thread was joined. Block dispatch."); - return; - } - - debug!("Thread created: {:?}", thread); - - let configuration_lock = get_configuration_lock(&ctx).await; - let configuration = configuration_lock.read().await; - - if let Some(introducer) = &configuration.thread_introductions.iter().find(|introducer| { - introducer.channels.iter().any(|channel_id| *channel_id == thread.parent_id.unwrap().0) - }) { - if let Err(why) = - thread.say(&ctx.http, &introducer.response.message.as_ref().unwrap()).await - { - error!("Error sending message: {:?}", why); - } - } - } - - async fn ready(&self, ctx: Context, ready: Ready) { - info!("Connected as {}", ready.user.name); - - for (cmd, description) in - [("repload", "Reloads the configuration."), ("stop", "Stop the Discord bot.")] - { - Command::create_global_application_command(&ctx.http, |command| { - command.name(cmd).description(description) - }) - .await - .expect("Could not create command."); - } - } +impl serenity::TypeMapKey for Configuration { + type Value = Arc>; } #[tokio::main] async fn main() { - // Initialize the logging framework. - logger::init(); - - // Set up the configuration. - let configuration = load_configuration(); - - // Load environment variables from .env file - dotenv::dotenv().ok(); - - // Get the Discord authorization token. - let token = env::var("DISCORD_AUTHORIZATION_TOKEN") - .expect("Could not load Discord authorization token"); - if token.len() != 70 { - error!("Invalid Discord authorization token."); - process::exit(1); - } - - // Create the Discord bot client. - let mut client = Client::builder( - &token, - GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT, - ) - .event_handler(Handler) - .await - .expect("Failed to create client"); - - // Save the configuration. - client.data.write().await.insert::(Arc::new(RwLock::new(configuration))); - - // Start the Discord bot. - client.start().await.expect("failed to start discord bot"); - - info!("Client started."); + // Initialize the logging framework + logger::init(); + + // Load environment variables from .env file + dotenv::dotenv().ok(); + + // Define poise framework commands (also in src/commands/mod.rs for serenity framework's manually dispatched events) + let mut commands = vec![ + configuration::register(), + configuration::reload(), + configuration::stop(), + ]; + poise::set_qualified_names(&mut commands); + + let configuration = Arc::new(RwLock::new(load_configuration())); + + let handler = Arc::new(Handler::new( + poise::FrameworkOptions { + commands, + on_error: |error| { + Box::pin(async { + poise::samples::on_error(error) + .await + .unwrap_or_else(|error| tracing::error!("{}", error)); + }) + }, + command_check: Some(|ctx| { + Box::pin(async move { + if let Some(member) = ctx.author_member().await { + let administrators = &ctx.data().read().await.administrators; + + if !(administrators + .users + // Check if the user is an administrator + .contains(&member.user.id.0) + || administrators + .roles + .iter() + // Has one of the administative roles + .any(|&role_id| { + member + .roles + .iter() + .any(|member_role| member_role.0 == role_id) + })) + { + return Ok(false); // Not an administrator, don't allow command execution + } + } + Ok(true) + }) + }), + listener: |_ctx, event, _framework, _data| { + Box::pin(async move { + tracing::info!("{:?}", event.name()); + Ok(()) + }) + }, + ..Default::default() + }, + configuration.clone(), // Pass configuration as user data for the framework + )); + + let mut client = serenity::Client::builder( + env::var("DISCORD_AUTHORIZATION_TOKEN") + .expect("Could not load Discord authorization token"), + serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT, + ) + .event_handler_arc(handler.clone()) + .await + .unwrap(); + + client + .data + .write() + .await + .insert::(configuration); + + handler + .set_shard_manager(client.shard_manager.clone()) + .await; + + client.start().await.unwrap(); } diff --git a/src/model/application.rs b/src/model/application.rs index 98cc43e..6bc5613 100644 --- a/src/model/application.rs +++ b/src/model/application.rs @@ -10,9 +10,10 @@ use serde::{Deserialize, Serialize}; #[derive(Default, Serialize, Deserialize)] pub struct Configuration { - pub administrators: Administrators, - pub thread_introductions: Vec, - pub message_responses: Vec, + pub general: General, + pub administrators: Administrators, + pub thread_introductions: Vec, + pub message_responses: Vec, } const CONFIG_PATH: &str = "configuration.json"; @@ -58,6 +59,11 @@ impl Configuration { } } +#[derive(Default, Serialize, Deserialize)] +pub struct General { + pub embed_color: i32, +} + #[derive(Default, Serialize, Deserialize)] pub struct Administrators { pub roles: Vec, diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..8fc6dbc --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,32 @@ +use poise::serenity_prelude::CreateEmbed; + +use crate::model::application::Configuration; + +pub(crate) fn load_configuration() -> Configuration { + Configuration::load().expect("Failed to load configuration") +} + +trait PoiseEmbed { + fn create_embed(self, embed: &mut CreateEmbed) -> &mut CreateEmbed; +} + +impl PoiseEmbed for crate::model::application::Embed { + fn create_embed(self, embed: &mut CreateEmbed) -> &mut CreateEmbed { + embed + .title(self.title) + .description(self.description) + .color(self.color) + .fields( + self.fields + .iter() + .map(|field| (field.name.clone(), field.value.clone(), field.inline)), + ) + .footer(|f| { + f.text(self.footer.text); + f.icon_url(self.footer.icon_url) + }) + .thumbnail(self.thumbnail.url) + .image(self.image.url) + .author(|a| a.name(self.author.name).icon_url(self.author.icon_url)) + } +} \ No newline at end of file