feat: migrate to command framework

This commit is contained in:
oSumAtrIX 2022-08-09 18:08:16 +02:00
parent 2aa1559df5
commit ba7b82a6de
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
16 changed files with 658 additions and 333 deletions

3
.vscode/launch.json vendored
View File

@ -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": [
{

View File

@ -1,3 +0,0 @@
{
"conventionalCommits.scopes": ["clippy"]
}

328
Cargo.lock generated
View File

@ -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 = "ryu"
version = "1.0.10"
name = "rustversion"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8"
[[package]]
name = "ryu"
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 = "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"
@ -936,10 +1094,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "syn"
version = "1.0.98"
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
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",
]

View File

@ -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"

View File

@ -1,5 +1,8 @@
{
"$schema": "./configuration.schema.json",
"general": {
"embed_color": 0
},
"administrators": {
"roles": [0],
"users": [0]

View File

@ -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": {

View File

@ -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(())
}

3
src/commands/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod configuration;
pub mod moderation;
pub mod utils;

View File

@ -0,0 +1 @@
// TODO mute/kick/ban/warn via database

1
src/commands/utils.rs Normal file
View File

@ -0,0 +1 @@
// TODO role assign buttons, embeds

View File

@ -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::<Utc>::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.");
}
}
}

94
src/events/mod.rs Normal file
View File

@ -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<RwLock<Configuration>> {
ctx.data
.read()
.await
.get::<Configuration>()
.expect("Expected Configuration in TypeMap.")
.clone()
}
pub struct Handler<T> {
options: poise::FrameworkOptions<T, Error>,
data: T,
bot_id: RwLock<Option<UserId>>,
shard_manager: RwLock<Option<Arc<Mutex<ShardManager>>>>,
}
// Custom handler to dispatch poise events
impl<T: Send + Sync> Handler<T> {
pub fn new(options: poise::FrameworkOptions<T, Error>, 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<Mutex<serenity::ShardManager>>) {
*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<Arc<RwLock<Configuration>>> {
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<serenity::Message>,
new: Option<serenity::Message>,
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;
}
}

View File

@ -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);
}
}
}

View File

@ -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<dyn std::error::Error + Send + Sync>;
type Context<'a> = poise::Context<'a, Arc<RwLock<Configuration>>, Error>;
impl TypeMapKey for BotConfiguration {
type Value = Arc<RwLock<Configuration>>;
}
pub struct Handler;
async fn get_configuration_lock(ctx: &Context) -> Arc<RwLock<Configuration>> {
ctx.data
.read()
.await
.get::<BotConfiguration>()
.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::<Utc>::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<RwLock<Configuration>>;
}
#[tokio::main]
async fn main() {
// Initialize the logging framework.
logger::init();
// Initialize the logging framework
logger::init();
// Set up the configuration.
let configuration = load_configuration();
// Load environment variables from .env file
dotenv::dotenv().ok();
// 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);
// 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);
}
let configuration = Arc::new(RwLock::new(load_configuration()));
// 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");
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;
// Save the configuration.
client.data.write().await.insert::<BotConfiguration>(Arc::new(RwLock::new(configuration)));
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
));
// Start the Discord bot.
client.start().await.expect("failed to start discord bot");
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();
info!("Client started.");
client
.data
.write()
.await
.insert::<Configuration>(configuration);
handler
.set_shard_manager(client.shard_manager.clone())
.await;
client.start().await.unwrap();
}

View File

@ -10,9 +10,10 @@ use serde::{Deserialize, Serialize};
#[derive(Default, Serialize, Deserialize)]
pub struct Configuration {
pub administrators: Administrators,
pub thread_introductions: Vec<Introduction>,
pub message_responses: Vec<MessageResponse>,
pub general: General,
pub administrators: Administrators,
pub thread_introductions: Vec<Introduction>,
pub message_responses: Vec<MessageResponse>,
}
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<u64>,

32
src/utils.rs Normal file
View File

@ -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))
}
}