mirror of
https://github.com/revanced/revanced-discord-bot.git
synced 2025-04-29 22:14:28 +02:00
feat: moderation commands
This commit is contained in:
parent
9407f1b37e
commit
54171ba2ce
32
.cargo/config.toml
Normal file
32
.cargo/config.toml
Normal file
@ -0,0 +1,32 @@
|
||||
[build]
|
||||
rustc-wrapper = 'P:\Programme (64 Bit)\Rust\.cargo\bin\sccache.exe'
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
rustflags = [
|
||||
'-Clink-arg=-fuse-ld=lld',
|
||||
'-Zshare-generics=y',
|
||||
]
|
||||
linker = '/usr/bin/clang'
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
rustflags = ['-Zshare-generics=y']
|
||||
linker = 'rust-lld.exe'
|
||||
|
||||
[target.x86_64-apple-darwin]
|
||||
rustflags = [
|
||||
'-C',
|
||||
'link-arg=-fuse-ld=/usr/local/bin/zld',
|
||||
'-Zshare-generics=y',
|
||||
'-Csplit-debuginfo=unpacked',
|
||||
]
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
debug = 2
|
||||
incremental = true
|
||||
codegen-units = 512
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = 0
|
||||
incremental = false
|
||||
codegen-units = 256
|
||||
split-debuginfo = '...'
|
@ -1,2 +1,4 @@
|
||||
# The Discord authorization token for the bot, requires the MESSAGE_CONTENT intent
|
||||
DISCORD_AUTHORIZATION_TOKEN=
|
||||
DISCORD_AUTHORIZATION_TOKEN=
|
||||
# The connection string to the MongoDB database
|
||||
MONGODB_URI=''
|
483
Cargo.lock
generated
483
Cargo.lock
generated
@ -8,6 +8,17 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
@ -80,6 +91,25 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bson"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d76085681585d39016f4d3841eb019201fc54d2dd0d92ad1e4fab3bfb32754"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"base64",
|
||||
"hex",
|
||||
"indexmap",
|
||||
"lazy_static",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"time 0.3.12",
|
||||
"uuid 1.1.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.10.0"
|
||||
@ -153,14 +183,38 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c"
|
||||
dependencies = [
|
||||
"darling_core 0.13.4",
|
||||
"darling_macro 0.13.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
"darling_core 0.14.1",
|
||||
"darling_macro 0.14.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -177,13 +231,24 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
|
||||
dependencies = [
|
||||
"darling_core 0.13.4",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.14.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_core 0.14.1",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
@ -201,6 +266,12 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
|
||||
|
||||
[[package]]
|
||||
name = "decancer"
|
||||
version = "1.4.1"
|
||||
@ -226,6 +297,7 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -263,6 +335,18 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-as-inner"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21cdad81446a7f7dc43f6a77409efeb9733d2fa65553efef6018ef257c959b73"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.24"
|
||||
@ -319,12 +403,34 @@ version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.21"
|
||||
@ -346,6 +452,7 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
@ -400,6 +507,12 @@ version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
@ -409,6 +522,32 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"match_cfg",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
@ -507,6 +646,18 @@ dependencies = [
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipconfig"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98"
|
||||
dependencies = [
|
||||
"socket2",
|
||||
"widestring",
|
||||
"winapi",
|
||||
"winreg 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.5.0"
|
||||
@ -540,6 +691,12 @@ version = "0.2.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
version = "0.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.7"
|
||||
@ -559,12 +716,36 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lru-cache"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
|
||||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "match_cfg"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
@ -608,6 +789,52 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mongodb"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b95afe97b0c799fdf69cd960272a2cb9662d077bd6efd84eb722bb9805d47554"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"base64",
|
||||
"bitflags",
|
||||
"bson",
|
||||
"chrono",
|
||||
"derivative",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"hmac",
|
||||
"lazy_static",
|
||||
"md-5",
|
||||
"os_info",
|
||||
"pbkdf2",
|
||||
"percent-encoding",
|
||||
"rand",
|
||||
"rustc_version_runtime",
|
||||
"rustls",
|
||||
"rustls-pemfile 0.3.0",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_with",
|
||||
"sha-1",
|
||||
"sha2",
|
||||
"socket2",
|
||||
"stringprep",
|
||||
"strsim",
|
||||
"take_mut",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"trust-dns-proto",
|
||||
"trust-dns-resolver",
|
||||
"typed-builder",
|
||||
"uuid 0.8.2",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-integer"
|
||||
version = "0.1.45"
|
||||
@ -661,6 +888,16 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5209b2162b2c140df493a93689e04f8deab3a67634f5bc7a553c0a98e5b8d399"
|
||||
dependencies = [
|
||||
"log",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.1"
|
||||
@ -684,6 +921,15 @@ dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
@ -727,7 +973,7 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ff861b6a52ec47bc54eb17424c025feeb040e82836036276c25dda045a8a0c"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"darling 0.14.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@ -748,6 +994,12 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.21"
|
||||
@ -849,7 +1101,7 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"rustls-pemfile 1.0.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
@ -862,17 +1114,29 @@ dependencies = [
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
"winreg",
|
||||
"winreg 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "resolv-conf"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
|
||||
dependencies = [
|
||||
"hostname",
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "revanced-discord-bot"
|
||||
version = "1.1.0"
|
||||
version = "2.2.1"
|
||||
dependencies = [
|
||||
"bson",
|
||||
"chrono",
|
||||
"decancer",
|
||||
"dirs",
|
||||
"dotenv",
|
||||
"mongodb",
|
||||
"poise",
|
||||
"regex",
|
||||
"serde",
|
||||
@ -898,6 +1162,25 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version_runtime"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d31b7153270ebf48bf91c65ae5b0c00e749c4cfad505f66530ac74950249582f"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.20.6"
|
||||
@ -910,6 +1193,15 @@ dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360"
|
||||
dependencies = [
|
||||
"base64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.1"
|
||||
@ -947,6 +1239,21 @@ dependencies = [
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.142"
|
||||
@ -966,6 +1273,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.142"
|
||||
@ -983,6 +1299,7 @@ version = "1.0.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
@ -1010,6 +1327,28 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_with_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_with_macros"
|
||||
version = "1.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082"
|
||||
dependencies = [
|
||||
"darling 0.13.4",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serenity"
|
||||
version = "0.11.5"
|
||||
@ -1053,6 +1392,17 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.4"
|
||||
@ -1093,12 +1443,28 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "stringprep"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.99"
|
||||
@ -1110,6 +1476,12 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "take_mut"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.32"
|
||||
@ -1161,8 +1533,15 @@ dependencies = [
|
||||
"libc",
|
||||
"num_threads",
|
||||
"serde",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
@ -1298,6 +1677,51 @@ dependencies = [
|
||||
"tracing-log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trust-dns-proto"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c31f240f59877c3d4bb3b3ea0ec5a6a0cff07323580ff8c7a605cd7d08b255d"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cfg-if",
|
||||
"data-encoding",
|
||||
"enum-as-inner",
|
||||
"futures-channel",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"idna",
|
||||
"ipnet",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"rand",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tinyvec",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trust-dns-resolver"
|
||||
version = "0.21.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4ba72c2ea84515690c9fcef4c6c660bb9df3036ed1051686de84605b74fd558"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"ipconfig",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"lru-cache",
|
||||
"parking_lot",
|
||||
"resolv-conf",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"trust-dns-proto",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.3"
|
||||
@ -1325,6 +1749,17 @@ dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-builder"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typemap_rev"
|
||||
version = "0.1.5"
|
||||
@ -1392,6 +1827,25 @@ version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
@ -1521,6 +1975,12 @@ dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "widestring"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@ -1586,6 +2046,15 @@ version = "0.36.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.10.1"
|
||||
|
10
Cargo.toml
10
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 = "1.1.0"
|
||||
version = "2.2.1"
|
||||
edition = "2021"
|
||||
|
||||
[profile.release]
|
||||
@ -17,8 +17,10 @@ codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[dependencies]
|
||||
poise = "0.3.0"
|
||||
decancer = "1.4.1"
|
||||
bson = "2.4"
|
||||
mongodb = "2.3"
|
||||
poise = "0.3"
|
||||
decancer = "1.4"
|
||||
tokio = { version = "1.20.1", features = ["rt-multi-thread"] }
|
||||
dotenv = "0.15"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
@ -26,6 +28,6 @@ serde_json = "1.0"
|
||||
regex = "1.0"
|
||||
serde_regex = "1.1"
|
||||
chrono = "0.4"
|
||||
dirs = "4.0.0"
|
||||
dirs = "4.0"
|
||||
tracing = { version = "0.1", features = ["max_level_debug", "release_max_level_info"] }
|
||||
tracing-subscriber = "0.3"
|
@ -1,7 +1,11 @@
|
||||
{
|
||||
"$schema": "./configuration.schema.json",
|
||||
"general": {
|
||||
"embed_color": 0
|
||||
"embed_color": 0,
|
||||
"mute": {
|
||||
"role": 0,
|
||||
"take": [0]
|
||||
}
|
||||
},
|
||||
"administrators": {
|
||||
"roles": [0],
|
||||
|
@ -1,6 +1,10 @@
|
||||
{
|
||||
{
|
||||
"general": {
|
||||
"embed_color": 14908858
|
||||
"embed_color": 14908858,
|
||||
"mute": {
|
||||
"role": 953984696491061289,
|
||||
"take": [996121272897519687]
|
||||
}
|
||||
},
|
||||
"administrators": {
|
||||
"roles": [973886585294704640],
|
||||
|
@ -10,6 +10,24 @@
|
||||
"properties": {
|
||||
"embed_color": {
|
||||
"$ref": "#/$defs/color"
|
||||
},
|
||||
"mute": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"role": {
|
||||
"type": "integer",
|
||||
"description": "The id of the role."
|
||||
},
|
||||
"take": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": "A list of role ids which will be revoked from the user.",
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1,18 +1,18 @@
|
||||
use tracing::debug;
|
||||
|
||||
use crate::utils::load_configuration;
|
||||
use crate::utils::bot::load_configuration;
|
||||
use crate::{Context, Error};
|
||||
|
||||
#[poise::command(slash_command, prefix_command)]
|
||||
#[poise::command(slash_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;
|
||||
*ctx.data().write().await.configuration.write().await = configuration;
|
||||
|
||||
debug!("{:?} reloaded the configuration.", ctx.author().name);
|
||||
debug!("{} reloaded the configuration.", ctx.author().name);
|
||||
|
||||
ctx.send(|f| {
|
||||
f.ephemeral(true).embed(|f| {
|
||||
@ -25,18 +25,31 @@ pub async fn reload(ctx: Context<'_>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[poise::command(slash_command, prefix_command)]
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn stop(ctx: Context<'_>) -> Result<(), Error> {
|
||||
debug!("{:?} stopped the bot.", ctx.author().name);
|
||||
debug!("{} stopped the bot.", ctx.author().name);
|
||||
|
||||
let color = ctx.data().read().await.general.embed_color;
|
||||
let color = ctx
|
||||
.data()
|
||||
.read()
|
||||
.await
|
||||
.configuration
|
||||
.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();
|
||||
ctx.framework()
|
||||
.shard_manager()
|
||||
.lock()
|
||||
.await
|
||||
.shutdown_all()
|
||||
.await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1 +1,290 @@
|
||||
// TODO mute/kick/ban/warn via database
|
||||
use bson::{doc, Document};
|
||||
use chrono::{Duration, Utc};
|
||||
use mongodb::options::{UpdateModifications, UpdateOptions};
|
||||
use poise::serenity_prelude::{self as serenity, Member, RoleId};
|
||||
use tracing::{debug, trace};
|
||||
|
||||
use crate::db::model::Muted;
|
||||
use crate::utils::moderation::{queue_unmute_member, respond_mute_command, ModerationKind};
|
||||
use crate::{Context, Error};
|
||||
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn unmute(
|
||||
ctx: Context<'_>,
|
||||
#[description = "The member to unmute"] member: Member,
|
||||
) -> Result<(), Error> {
|
||||
ctx.defer().await.expect("Failed to defer");
|
||||
|
||||
let data = &ctx.data().read().await;
|
||||
let configuration = data.configuration.read().await;
|
||||
|
||||
if let Some(pending_unmute) = data.pending_unmutes.read().await.get(&member.user.id.0) {
|
||||
trace!("Cancelling pending unmute for {}", member.user.id.0);
|
||||
pending_unmute.abort();
|
||||
}
|
||||
|
||||
respond_mute_command(
|
||||
&ctx,
|
||||
ModerationKind::Unmute(
|
||||
queue_unmute_member(
|
||||
ctx.discord(),
|
||||
&data.database,
|
||||
&member,
|
||||
configuration.general.mute.role,
|
||||
0,
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
),
|
||||
&member.user,
|
||||
configuration.general.embed_color,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn mute(
|
||||
ctx: Context<'_>,
|
||||
#[description = "The member to mute"] mut member: Member,
|
||||
#[description = "Seconds"] seconds: Option<i64>,
|
||||
#[description = "Minutes"] minutes: Option<i64>,
|
||||
#[description = "Hours"] hours: Option<i64>,
|
||||
#[description = "Days"] days: Option<i64>,
|
||||
#[description = "Months"] months: Option<i64>,
|
||||
#[description = "Months"] reason: String,
|
||||
) -> Result<(), Error> {
|
||||
let now = Utc::now();
|
||||
let mut mute_duration = Duration::zero();
|
||||
|
||||
if let Some(seconds) = seconds {
|
||||
mute_duration = mute_duration
|
||||
.checked_add(&Duration::seconds(seconds))
|
||||
.unwrap();
|
||||
}
|
||||
if let Some(minutes) = minutes {
|
||||
mute_duration = mute_duration
|
||||
.checked_add(&Duration::minutes(minutes))
|
||||
.unwrap();
|
||||
}
|
||||
if let Some(hours) = hours {
|
||||
mute_duration = mute_duration.checked_add(&Duration::hours(hours)).unwrap();
|
||||
}
|
||||
if let Some(days) = days {
|
||||
mute_duration = mute_duration.checked_add(&Duration::days(days)).unwrap();
|
||||
}
|
||||
if let Some(months) = months {
|
||||
const DAYS_IN_MONTH: i64 = 30;
|
||||
mute_duration = mute_duration
|
||||
.checked_add(&Duration::days(months * DAYS_IN_MONTH))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
let unmute_time = now + mute_duration;
|
||||
|
||||
let data = ctx.data().read().await;
|
||||
let configuration = data.configuration.read().await;
|
||||
let embed_color = configuration.general.embed_color;
|
||||
let mute = &configuration.general.mute;
|
||||
let mute_role_id = mute.role;
|
||||
let take = &mute.take;
|
||||
let is_currently_muted = member.roles.iter().any(|r| r.0 == mute_role_id);
|
||||
|
||||
let result =
|
||||
if let Err(add_role_result) = member.add_role(&ctx.discord().http, mute_role_id).await {
|
||||
Some(Error::from(add_role_result))
|
||||
} else {
|
||||
let removed_roles = member
|
||||
.roles
|
||||
.iter()
|
||||
.filter(|r| take.contains(&r.0))
|
||||
.map(|r| r.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let removed = member
|
||||
.remove_roles(
|
||||
&ctx.discord().http,
|
||||
&take.iter().map(|&r| RoleId::from(r)).collect::<Vec<_>>(),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(remove_role_result) = removed {
|
||||
Some(Error::from(remove_role_result))
|
||||
} else {
|
||||
// Roles which were removed from the user
|
||||
let updated: Document = Muted {
|
||||
guild_id: Some(member.guild_id.0.to_string()),
|
||||
expires: Some(unmute_time.timestamp() as u64),
|
||||
reason: Some(reason.clone()),
|
||||
taken_roles: if is_currently_muted {
|
||||
// Prevent the bot from overriding the "take" field.
|
||||
// This would happen otherwise, because the bot would accumulate the users roles and then override the value in the database
|
||||
// resulting in the user being muted to have no roles to add back later.
|
||||
None
|
||||
} else {
|
||||
Some(removed_roles)
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
.into();
|
||||
|
||||
if let Err(database_update_result) = data
|
||||
.database
|
||||
.update::<Muted>(
|
||||
"muted",
|
||||
Muted {
|
||||
user_id: Some(member.user.id.0.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
.into(),
|
||||
UpdateModifications::Document(doc! { "$set": updated}),
|
||||
Some(UpdateOptions::builder().upsert(true).build()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Some(database_update_result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(pending_unmute) = data.pending_unmutes.read().await.get(&member.user.id.0) {
|
||||
trace!("Cancelling pending unmute for {}", member.user.id.0);
|
||||
pending_unmute.abort();
|
||||
}
|
||||
|
||||
data.pending_unmutes.write().await.insert(
|
||||
member.user.id.0,
|
||||
queue_unmute_member(
|
||||
ctx.discord(),
|
||||
&data.database,
|
||||
&member,
|
||||
mute_role_id,
|
||||
mute_duration.num_seconds() as u64,
|
||||
),
|
||||
);
|
||||
|
||||
respond_mute_command(
|
||||
&ctx,
|
||||
ModerationKind::Mute(
|
||||
reason,
|
||||
unmute_time.format("%d/%m/%Y %H:%M").to_string(),
|
||||
result,
|
||||
),
|
||||
&member.user,
|
||||
embed_color,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Delete recent messages of a user. Cannot delete messages older than 14 days.
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn purge(
|
||||
ctx: Context<'_>,
|
||||
#[description = "User"] member: Option<Member>,
|
||||
#[description = "Until message"] until: Option<String>,
|
||||
#[min = 1]
|
||||
#[max = 1000]
|
||||
#[description = "Count"]
|
||||
count: Option<i64>,
|
||||
) -> Result<(), Error> {
|
||||
// The maximum amount of times to page through messages. If paged over MAX_PAGES amount of times without deleting messages, break.
|
||||
const MAX_PAGES: i8 = 2;
|
||||
// The maximal amount of messages that we can fetch at all
|
||||
const MAX_BULK_DELETE: i64 = 100;
|
||||
// Discord does not let us bulk-delete messages older than 14 days
|
||||
const MAX_BULK_DELETE_AGO_SECS: i64 = 60 * 60 * 24 * 14;
|
||||
|
||||
let data = ctx.data().read().await;
|
||||
let configuration = data.configuration.read().await;
|
||||
let embed_color = configuration.general.embed_color;
|
||||
let channel = ctx.channel_id();
|
||||
let too_old_timestamp = Utc::now().timestamp() - MAX_BULK_DELETE_AGO_SECS;
|
||||
|
||||
let user = ctx.discord().http.get_current_user().await?;
|
||||
let image = user
|
||||
.avatar_url()
|
||||
.unwrap_or_else(|| user.default_avatar_url());
|
||||
|
||||
let handle = ctx
|
||||
.send(|f| {
|
||||
f.embed(|f| {
|
||||
f.title("Purging messages")
|
||||
.description("Accumulating...")
|
||||
.color(embed_color)
|
||||
.thumbnail(&image)
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
let mut response = handle.message().await?;
|
||||
|
||||
ctx.defer().await?;
|
||||
|
||||
let count_to_delete = count.unwrap_or(MAX_BULK_DELETE) as usize;
|
||||
let mut deleted_amount = 0;
|
||||
let mut empty_pages: i8 = 0;
|
||||
|
||||
loop {
|
||||
// Filter out messages that are too old
|
||||
let mut messages = channel
|
||||
.messages(&ctx.discord(), |m| {
|
||||
m.limit(count_to_delete as u64).before(response.id)
|
||||
})
|
||||
.await?
|
||||
.into_iter()
|
||||
.take_while(|m| m.timestamp.timestamp() > too_old_timestamp)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Filter for messages from the user
|
||||
if let Some(ref member) = member {
|
||||
messages = messages
|
||||
.into_iter()
|
||||
.filter(|msg| msg.author.id == member.user.id)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
debug!("Filtered messages by {}. Left: {}", member, messages.len());
|
||||
}
|
||||
|
||||
// Filter for messages until the g/mutiven id
|
||||
if let Some(ref message_id) = until {
|
||||
if let Ok(message_id) = message_id.parse::<u64>() {
|
||||
messages = messages
|
||||
.into_iter()
|
||||
.take_while(|m| m.id.0 > message_id)
|
||||
.collect::<Vec<_>>();
|
||||
debug!(
|
||||
"Filtered messages until {}. Left: {}",
|
||||
message_id,
|
||||
messages.len()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let purge_count = messages.len();
|
||||
if purge_count > 0 {
|
||||
deleted_amount += purge_count;
|
||||
channel.delete_messages(&ctx.discord(), &messages).await?;
|
||||
} else {
|
||||
empty_pages += 1;
|
||||
}
|
||||
|
||||
if empty_pages >= MAX_PAGES || deleted_amount >= count_to_delete {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
response
|
||||
.to_mut()
|
||||
.edit(&ctx.discord(), |e| {
|
||||
e.set_embed(
|
||||
serenity::CreateEmbed::default()
|
||||
.title("Purge successful")
|
||||
.field("Deleted messages", deleted_amount.to_string(), false)
|
||||
.color(embed_color)
|
||||
.thumbnail(image)
|
||||
.clone(),
|
||||
)
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
106
src/db/database.rs
Normal file
106
src/db/database.rs
Normal file
@ -0,0 +1,106 @@
|
||||
use std::error::Error;
|
||||
|
||||
use bson::Document;
|
||||
use mongodb::options::{
|
||||
ClientOptions,
|
||||
DeleteOptions,
|
||||
FindOneAndDeleteOptions,
|
||||
FindOptions,
|
||||
InsertOneOptions,
|
||||
ResolverConfig,
|
||||
UpdateModifications,
|
||||
UpdateOptions,
|
||||
};
|
||||
use mongodb::results::{DeleteResult, InsertOneResult, UpdateResult};
|
||||
use mongodb::{Client, Collection, Cursor};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Database {
|
||||
client: Client,
|
||||
database: String,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub async fn new(connection: &str, database: &str) -> Result<Database, Box<dyn Error>> {
|
||||
let options =
|
||||
ClientOptions::parse_with_resolver_config(&connection, ResolverConfig::cloudflare())
|
||||
.await?;
|
||||
let client = Client::with_options(options)?;
|
||||
|
||||
Ok(Database {
|
||||
client,
|
||||
database: database.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn open<T>(&self, collection: &str) -> Collection<T> {
|
||||
self.client.database(&self.database).collection(collection)
|
||||
}
|
||||
|
||||
pub async fn update<T>(
|
||||
&self,
|
||||
collection: &str,
|
||||
query: Document,
|
||||
update_modifications: UpdateModifications,
|
||||
options: Option<UpdateOptions>,
|
||||
) -> Result<UpdateResult, Box<dyn Error + Send + Sync>> {
|
||||
let result = self
|
||||
.open::<T>(collection)
|
||||
.update_one(query, update_modifications, options)
|
||||
.await?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn find<T>(
|
||||
&self,
|
||||
collection: &str,
|
||||
filter: Document,
|
||||
options: Option<FindOptions>,
|
||||
) -> Result<Cursor<T>, Box<dyn Error + Send + Sync>> {
|
||||
let cursor = self.open(collection).find(filter, options).await?;
|
||||
|
||||
Ok(cursor)
|
||||
}
|
||||
|
||||
pub async fn find_and_delete<T: DeserializeOwned>(
|
||||
&self,
|
||||
collection: &str,
|
||||
filter: Document,
|
||||
options: Option<FindOneAndDeleteOptions>,
|
||||
) -> Result<Option<T>, Box<dyn Error + Send + Sync>> {
|
||||
let result = self
|
||||
.open(collection)
|
||||
.find_one_and_delete(filter, options)
|
||||
.await?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn insert<T: Serialize>(
|
||||
&self,
|
||||
collection: &str,
|
||||
doc: T,
|
||||
options: Option<InsertOneOptions>,
|
||||
) -> Result<InsertOneResult, Box<dyn Error + Send + Sync>> {
|
||||
let result = self.open(collection).insert_one(doc, options).await?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn delete(
|
||||
&self,
|
||||
collection: &str,
|
||||
query: Document,
|
||||
options: Option<DeleteOptions>,
|
||||
) -> Result<DeleteResult, Box<dyn Error + Send + Sync>> {
|
||||
let result = self
|
||||
.open::<Document>(collection)
|
||||
.delete_one(query, options)
|
||||
.await?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
2
src/db/mod.rs
Normal file
2
src/db/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod database;
|
||||
pub mod model;
|
32
src/db/model.rs
Normal file
32
src/db/model.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use bson::Document;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// Models
|
||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||
pub struct Muted {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub user_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub guild_id: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub taken_roles: Option<Vec<String>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub expires: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
impl From<Muted> for Document {
|
||||
fn from(muted: Muted) -> Self {
|
||||
bson::to_document(&muted).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
// Display trait
|
||||
impl Display for Muted {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "muted")
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
use super::*;
|
||||
use crate::utils::decancer::cure;
|
||||
|
||||
pub async fn guild_member_addition(ctx: &serenity::Context, new_member: &serenity::Member) {
|
||||
crate::utils::cure(ctx, new_member).await;
|
||||
}
|
||||
cure(ctx, new_member).await;
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
use super::*;
|
||||
use crate::utils::decancer::cure;
|
||||
|
||||
pub async fn guild_member_update(
|
||||
ctx: &serenity::Context,
|
||||
_old_if_available: &Option<serenity::Member>,
|
||||
new: &serenity::Member,
|
||||
) {
|
||||
crate::utils::cure(ctx, new).await;
|
||||
}
|
||||
cure(ctx, new).await;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ use regex::Regex;
|
||||
use tracing::debug;
|
||||
|
||||
use super::*;
|
||||
use crate::utils::bot::get_data_lock;
|
||||
|
||||
fn contains_match(regex: &[Regex], text: &str) -> bool {
|
||||
regex.iter().any(|r| r.is_match(text))
|
||||
@ -14,10 +15,13 @@ pub async fn message_create(ctx: &serenity::Context, new_message: &serenity::Mes
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(message_response) = get_configuration_lock(ctx)
|
||||
if let Some(message_response) = get_data_lock(ctx)
|
||||
.await
|
||||
.read()
|
||||
.await
|
||||
.configuration
|
||||
.read()
|
||||
.await
|
||||
.message_responses
|
||||
.iter()
|
||||
.find(|&response| {
|
||||
|
@ -2,24 +2,14 @@ use std::sync::Arc;
|
||||
|
||||
use poise::serenity_prelude::{self as serenity, Mutex, RwLock, ShardManager, UserId};
|
||||
|
||||
use crate::model::application::Configuration;
|
||||
use crate::Error;
|
||||
use crate::{Data, Error};
|
||||
|
||||
mod guild_member_addition;
|
||||
mod guild_member_update;
|
||||
mod message_create;
|
||||
mod ready;
|
||||
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,
|
||||
@ -55,9 +45,11 @@ impl<T: Send + Sync> Handler<T> {
|
||||
|
||||
// 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) {
|
||||
impl serenity::EventHandler for Handler<Arc<RwLock<Data>>> {
|
||||
async fn ready(&self, ctx: serenity::Context, ready: serenity::Ready) {
|
||||
*self.bot_id.write().await = Some(ready.user.id);
|
||||
|
||||
ready::load_muted_members(&ctx, &ready).await;
|
||||
}
|
||||
|
||||
async fn message(&self, ctx: serenity::Context, new_message: serenity::Message) {
|
||||
|
59
src/events/ready.rs
Normal file
59
src/events/ready.rs
Normal file
@ -0,0 +1,59 @@
|
||||
use chrono::Utc;
|
||||
use tracing::trace;
|
||||
|
||||
use super::*;
|
||||
use crate::db::model::Muted;
|
||||
use crate::utils::bot::get_data_lock;
|
||||
use crate::utils::moderation::queue_unmute_member;
|
||||
|
||||
pub async fn load_muted_members(ctx: &serenity::Context, _: &serenity::Ready) {
|
||||
let data = get_data_lock(ctx).await;
|
||||
let data = data.read().await;
|
||||
let database = &data.database;
|
||||
let mute_role_id = data.configuration.read().await.general.mute.role;
|
||||
let mut pending_unmutes = data.pending_unmutes.write().await;
|
||||
|
||||
let mut cursor = database
|
||||
.find::<Muted>(
|
||||
"muted",
|
||||
Muted {
|
||||
..Default::default()
|
||||
}
|
||||
.into(),
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let http_ref = &ctx.http;
|
||||
|
||||
while cursor.advance().await.unwrap() {
|
||||
let current: Muted = cursor.deserialize_current().unwrap();
|
||||
let guild_id = current.guild_id.unwrap().parse::<u64>().unwrap();
|
||||
let member_id = current.user_id.unwrap().parse::<u64>().unwrap();
|
||||
|
||||
if let Ok(member) = http_ref
|
||||
.get_guild(guild_id)
|
||||
.await
|
||||
.unwrap()
|
||||
.member(http_ref, member_id)
|
||||
.await
|
||||
{
|
||||
let amount_left =
|
||||
std::cmp::max(current.expires.unwrap() as i64 - Utc::now().timestamp(), 0);
|
||||
|
||||
pending_unmutes.insert(
|
||||
member.user.id.0,
|
||||
queue_unmute_member(
|
||||
ctx,
|
||||
database,
|
||||
&member,
|
||||
mute_role_id,
|
||||
amount_left as u64, // i64 as u64 is handled properly here
|
||||
),
|
||||
);
|
||||
} else {
|
||||
trace!("Failed to find member {} in guild {}", member_id, guild_id);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
use tracing::{debug, error};
|
||||
|
||||
use super::*;
|
||||
use crate::utils::bot::get_data_lock;
|
||||
|
||||
pub async fn thread_create(ctx: &serenity::Context, thread: &serenity::GuildChannel) {
|
||||
if thread.member.is_some() {
|
||||
@ -10,8 +11,14 @@ pub async fn thread_create(ctx: &serenity::Context, thread: &serenity::GuildChan
|
||||
|
||||
debug!("Thread created: {:?}", thread);
|
||||
|
||||
let configuration_lock = get_configuration_lock(ctx).await;
|
||||
let thread_introductions = &configuration_lock.read().await.thread_introductions;
|
||||
let data_lock = get_data_lock(ctx).await;
|
||||
let configuration_lock = data_lock.read().await;
|
||||
|
||||
let thread_introductions = &configuration_lock
|
||||
.configuration
|
||||
.read()
|
||||
.await
|
||||
.thread_introductions;
|
||||
|
||||
if let Some(introducer) = thread_introductions.iter().find(|introducer| {
|
||||
introducer
|
||||
|
@ -1,4 +1,4 @@
|
||||
pub fn init() {
|
||||
// TODO: log to file
|
||||
tracing_subscriber::fmt::init();
|
||||
// TODO: log to file
|
||||
tracing_subscriber::fmt::init();
|
||||
}
|
||||
|
85
src/main.rs
85
src/main.rs
@ -1,24 +1,35 @@
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::sync::Arc;
|
||||
|
||||
use commands::configuration;
|
||||
use commands::{configuration, moderation};
|
||||
use db::database::Database;
|
||||
use events::Handler;
|
||||
use poise::serenity_prelude::{self as serenity, RwLock};
|
||||
use utils::load_configuration;
|
||||
use poise::serenity_prelude::{self as serenity, RwLock, UserId};
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{error, trace};
|
||||
use utils::bot::load_configuration;
|
||||
|
||||
use crate::model::application::Configuration;
|
||||
|
||||
mod commands;
|
||||
mod db;
|
||||
mod events;
|
||||
mod logger;
|
||||
mod model;
|
||||
mod utils;
|
||||
|
||||
type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||
type Context<'a> = poise::Context<'a, Arc<RwLock<Configuration>>, Error>;
|
||||
type Context<'a> = poise::Context<'a, Arc<RwLock<Data>>, Error>;
|
||||
|
||||
impl serenity::TypeMapKey for Configuration {
|
||||
type Value = Arc<RwLock<Configuration>>;
|
||||
impl serenity::TypeMapKey for Data {
|
||||
type Value = Arc<RwLock<Data>>;
|
||||
}
|
||||
|
||||
pub struct Data {
|
||||
configuration: Arc<RwLock<Configuration>>,
|
||||
database: Arc<Database>,
|
||||
pending_unmutes: Arc<RwLock<HashMap<u64, JoinHandle<Option<Error>>>>>,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
@ -34,13 +45,40 @@ async fn main() {
|
||||
configuration::register(),
|
||||
configuration::reload(),
|
||||
configuration::stop(),
|
||||
moderation::mute(),
|
||||
moderation::unmute(),
|
||||
moderation::purge(),
|
||||
];
|
||||
poise::set_qualified_names(&mut commands);
|
||||
|
||||
let configuration = Arc::new(RwLock::new(load_configuration()));
|
||||
let configuration = load_configuration();
|
||||
|
||||
let owners = configuration
|
||||
.administrators
|
||||
.users
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(UserId)
|
||||
.collect::<Vec<UserId>>()
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
let data = Arc::new(RwLock::new(Data {
|
||||
configuration: Arc::new(RwLock::new(configuration)),
|
||||
database: Arc::new(
|
||||
Database::new(
|
||||
&env::var("MONGODB_URI").expect("MONGODB_URI environment variable not set"),
|
||||
"revanced_discord_bot",
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
),
|
||||
pending_unmutes: Arc::new(RwLock::new(HashMap::new())),
|
||||
}));
|
||||
|
||||
let handler = Arc::new(Handler::new(
|
||||
poise::FrameworkOptions {
|
||||
owners,
|
||||
commands,
|
||||
on_error: |error| {
|
||||
Box::pin(async {
|
||||
@ -52,7 +90,9 @@ async fn main() {
|
||||
command_check: Some(|ctx| {
|
||||
Box::pin(async move {
|
||||
if let Some(member) = ctx.author_member().await {
|
||||
let administrators = &ctx.data().read().await.administrators;
|
||||
let data_lock = &ctx.data().read().await;
|
||||
let configuration = &data_lock.configuration.read().await;
|
||||
let administrators = &configuration.administrators;
|
||||
|
||||
if !(administrators
|
||||
.users
|
||||
@ -69,6 +109,25 @@ async fn main() {
|
||||
.any(|member_role| member_role.0 == role_id)
|
||||
}))
|
||||
{
|
||||
if let Err(e) = ctx
|
||||
.channel_id()
|
||||
.send_message(&ctx.discord().http, |m| {
|
||||
m.embed(|e| {
|
||||
e.title("Permission error")
|
||||
.description(
|
||||
"You do not have permission to use this command.",
|
||||
)
|
||||
.color(configuration.general.embed_color)
|
||||
.thumbnail(member.user.avatar_url().unwrap_or_else(
|
||||
|| member.user.default_avatar_url(),
|
||||
))
|
||||
})
|
||||
})
|
||||
.await
|
||||
{
|
||||
error!("Error sending message: {:?}", e)
|
||||
}
|
||||
trace!("{} is not an administrator.", member.user.name);
|
||||
return Ok(false); // Not an administrator, don't allow command execution
|
||||
}
|
||||
}
|
||||
@ -83,12 +142,12 @@ async fn main() {
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
configuration.clone(), // Pass configuration as user data for the framework
|
||||
data.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"),
|
||||
.expect("DISCORD_AUTHORIZATION_TOKEN environment variable not set"),
|
||||
serenity::GatewayIntents::non_privileged()
|
||||
| serenity::GatewayIntents::MESSAGE_CONTENT
|
||||
| serenity::GatewayIntents::GUILD_MEMBERS,
|
||||
@ -97,11 +156,7 @@ async fn main() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
client
|
||||
.data
|
||||
.write()
|
||||
.await
|
||||
.insert::<Configuration>(configuration);
|
||||
client.data.write().await.insert::<Data>(data);
|
||||
|
||||
handler
|
||||
.set_shard_manager(client.shard_manager.clone())
|
||||
|
@ -1,8 +1,6 @@
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::{Read, Result, Write},
|
||||
path::Path,
|
||||
};
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Read, Result, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use dirs::config_dir;
|
||||
use regex::Regex;
|
||||
@ -19,139 +17,150 @@ pub struct Configuration {
|
||||
const CONFIG_PATH: &str = "configuration.json";
|
||||
|
||||
impl Configuration {
|
||||
fn save(&self) -> Result<()> {
|
||||
let sys_config_dir = config_dir().expect("find config dir");
|
||||
fn save(&self) -> Result<()> {
|
||||
let sys_config_dir = config_dir().expect("find config dir");
|
||||
|
||||
fs::create_dir_all(format!("{}/revanced-discord-bot", sys_config_dir.to_string_lossy()))
|
||||
.expect("create config dir");
|
||||
fs::create_dir_all(format!(
|
||||
"{}/revanced-discord-bot",
|
||||
sys_config_dir.to_string_lossy()
|
||||
))
|
||||
.expect("create config dir");
|
||||
|
||||
let mut file = File::create(CONFIG_PATH)?;
|
||||
let json = serde_json::to_string_pretty(&self)?;
|
||||
file.write_all(json.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
let mut file = File::create(CONFIG_PATH)?;
|
||||
let json = serde_json::to_string_pretty(&self)?;
|
||||
file.write_all(json.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load() -> Result<Configuration> {
|
||||
let sys_config_dir = config_dir().expect("Can not find the configuration directory.");
|
||||
let sys_config =
|
||||
format!("{}/revanced-discord-bot/{CONFIG_PATH}", sys_config_dir.to_string_lossy());
|
||||
pub fn load() -> Result<Configuration> {
|
||||
let sys_config_dir = config_dir().expect("Can not find the configuration directory.");
|
||||
let sys_config = format!(
|
||||
"{}/revanced-discord-bot/{CONFIG_PATH}",
|
||||
sys_config_dir.to_string_lossy()
|
||||
);
|
||||
|
||||
// config file in current dir
|
||||
let mut file = if Path::new(CONFIG_PATH).exists() {
|
||||
File::open(CONFIG_PATH)?
|
||||
}
|
||||
// config file in system dir (on *nix: `~/.config/revanced-discord-bot/`)
|
||||
else if Path::new(&sys_config).exists() {
|
||||
File::open(sys_config)?
|
||||
}
|
||||
// create defalt config
|
||||
else {
|
||||
let default_config = Configuration::default();
|
||||
default_config.save()?;
|
||||
// config file in current dir
|
||||
let mut file = if Path::new(CONFIG_PATH).exists() {
|
||||
File::open(CONFIG_PATH)?
|
||||
}
|
||||
// config file in system dir (on *nix: `~/.config/revanced-discord-bot/`)
|
||||
else if Path::new(&sys_config).exists() {
|
||||
File::open(sys_config)?
|
||||
}
|
||||
// create defalt config
|
||||
else {
|
||||
let default_config = Configuration::default();
|
||||
default_config.save()?;
|
||||
|
||||
File::open(sys_config)?
|
||||
};
|
||||
File::open(sys_config)?
|
||||
};
|
||||
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf)?;
|
||||
let mut buf = String::new();
|
||||
file.read_to_string(&mut buf)?;
|
||||
|
||||
Ok(serde_json::from_str(&buf)?)
|
||||
}
|
||||
Ok(serde_json::from_str(&buf)?)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct General {
|
||||
pub embed_color: i32,
|
||||
pub mute: Mute,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct Mute {
|
||||
pub role: u64,
|
||||
pub take: Vec<u64>,
|
||||
}
|
||||
#[derive(Default, Serialize, Deserialize)]
|
||||
pub struct Administrators {
|
||||
pub roles: Vec<u64>,
|
||||
pub users: Vec<u64>,
|
||||
pub roles: Vec<u64>,
|
||||
pub users: Vec<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Introduction {
|
||||
pub channels: Vec<u64>,
|
||||
pub response: Response,
|
||||
pub channels: Vec<u64>,
|
||||
pub response: Response,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct MessageResponse {
|
||||
pub includes: Includes,
|
||||
pub excludes: Excludes,
|
||||
pub condition: Condition,
|
||||
pub response: Response,
|
||||
pub includes: Includes,
|
||||
pub excludes: Excludes,
|
||||
pub condition: Condition,
|
||||
pub response: Response,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Response {
|
||||
pub message: Option<String>,
|
||||
pub embed: Option<Embed>,
|
||||
pub message: Option<String>,
|
||||
pub embed: Option<Embed>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Embed {
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub color: i32,
|
||||
pub fields: Vec<Field>,
|
||||
pub footer: Footer,
|
||||
pub image: Image,
|
||||
pub thumbnail: Thumbnail,
|
||||
pub author: Author,
|
||||
pub title: String,
|
||||
pub description: String,
|
||||
pub color: i32,
|
||||
pub fields: Vec<Field>,
|
||||
pub footer: Footer,
|
||||
pub image: Image,
|
||||
pub thumbnail: Thumbnail,
|
||||
pub author: Author,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Field {
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
pub inline: bool,
|
||||
pub name: String,
|
||||
pub value: String,
|
||||
pub inline: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Footer {
|
||||
pub text: String,
|
||||
pub icon_url: String,
|
||||
pub text: String,
|
||||
pub icon_url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Image {
|
||||
pub url: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Thumbnail {
|
||||
pub url: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Author {
|
||||
pub name: String,
|
||||
pub icon_url: String,
|
||||
pub url: String,
|
||||
pub name: String,
|
||||
pub icon_url: String,
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Includes {
|
||||
pub channels: Vec<u64>,
|
||||
#[serde(rename = "match", with = "serde_regex")]
|
||||
pub match_field: Vec<Regex>,
|
||||
pub channels: Vec<u64>,
|
||||
#[serde(rename = "match", with = "serde_regex")]
|
||||
pub match_field: Vec<Regex>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Excludes {
|
||||
pub roles: Vec<u64>,
|
||||
#[serde(rename = "match", with = "serde_regex")]
|
||||
pub match_field: Vec<Regex>,
|
||||
pub roles: Vec<u64>,
|
||||
#[serde(rename = "match", with = "serde_regex")]
|
||||
pub match_field: Vec<Regex>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Condition {
|
||||
pub user: User,
|
||||
pub user: User,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct User {
|
||||
pub server_age: i64,
|
||||
pub server_age: i64,
|
||||
}
|
||||
|
58
src/utils.rs
58
src/utils.rs
@ -1,58 +0,0 @@
|
||||
use decancer::Decancer;
|
||||
use poise::serenity_prelude::{self as serenity, CreateEmbed};
|
||||
use tracing::{error, info};
|
||||
|
||||
use crate::model::application::Configuration;
|
||||
|
||||
const DECANCER: Decancer = Decancer::new();
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn cure(ctx: &serenity::Context, member: &serenity::Member) {
|
||||
let display_name = member.display_name();
|
||||
let name = display_name.to_string();
|
||||
|
||||
let cured_user_name = DECANCER.cure(&name);
|
||||
|
||||
if name.to_lowercase() == cured_user_name {
|
||||
return; // username is already cured
|
||||
}
|
||||
|
||||
match member
|
||||
.guild_id
|
||||
.edit_member(&ctx.http, member.user.id, |edit_member| {
|
||||
edit_member.nickname(cured_user_name)
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => info!("Cured user {}", member.user.tag()),
|
||||
Err(err) => error!("Failed to cure user {}: {}", name, err),
|
||||
}
|
||||
}
|
20
src/utils/bot.rs
Normal file
20
src/utils/bot.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use poise::serenity_prelude::{self as serenity, RwLock};
|
||||
|
||||
use crate::model::application::Configuration;
|
||||
use crate::Data;
|
||||
|
||||
pub fn load_configuration() -> Configuration {
|
||||
Configuration::load().expect("Failed to load configuration")
|
||||
}
|
||||
|
||||
// Share the lock reference between the threads in serenity framework
|
||||
pub async fn get_data_lock(ctx: &serenity::Context) -> Arc<RwLock<Data>> {
|
||||
ctx.data
|
||||
.read()
|
||||
.await
|
||||
.get::<Data>()
|
||||
.expect("Expected Configuration in TypeMap.")
|
||||
.clone()
|
||||
}
|
28
src/utils/decancer.rs
Normal file
28
src/utils/decancer.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use ::decancer::Decancer;
|
||||
use tracing::{error, info};
|
||||
|
||||
use super::*;
|
||||
|
||||
const DECANCER: Decancer = Decancer::new();
|
||||
|
||||
pub async fn cure(ctx: &serenity::Context, member: &serenity::Member) {
|
||||
let display_name = member.display_name();
|
||||
let name = display_name.to_string();
|
||||
|
||||
let cured_user_name = DECANCER.cure(&name);
|
||||
|
||||
if name.to_lowercase() == cured_user_name {
|
||||
return; // username is already cured
|
||||
}
|
||||
|
||||
match member
|
||||
.guild_id
|
||||
.edit_member(&ctx.http, member.user.id, |edit_member| {
|
||||
edit_member.nickname(cured_user_name)
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => info!("Cured user {}", member.user.tag()),
|
||||
Err(err) => error!("Failed to cure user {}: {}", name, err),
|
||||
}
|
||||
}
|
26
src/utils/embed.rs
Normal file
26
src/utils/embed.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use poise::serenity_prelude::CreateEmbed;
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
6
src/utils/mod.rs
Normal file
6
src/utils/mod.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use poise::serenity_prelude::{self as serenity, Member, RoleId};
|
||||
|
||||
pub mod bot;
|
||||
pub mod decancer;
|
||||
pub mod embed;
|
||||
pub mod moderation;
|
102
src/utils/moderation.rs
Normal file
102
src/utils/moderation.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use super::*;
|
||||
use crate::db::database::Database;
|
||||
use crate::db::model::Muted;
|
||||
use crate::{Context, Error};
|
||||
|
||||
pub enum ModerationKind {
|
||||
Mute(String, String, Option<Error>), // Reason, Expires, Error
|
||||
Unmute(Option<Error>), // Error
|
||||
}
|
||||
|
||||
pub fn queue_unmute_member(
|
||||
ctx: &serenity::Context,
|
||||
database: &Database,
|
||||
member: &Member,
|
||||
mute_role_id: u64,
|
||||
mute_duration: u64,
|
||||
) -> JoinHandle<Option<Error>> {
|
||||
let ctx = ctx.clone();
|
||||
let database = database.clone();
|
||||
let mut member = member.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(mute_duration)).await;
|
||||
|
||||
let delete_result = database
|
||||
.find_and_delete::<Muted>(
|
||||
"muted",
|
||||
Muted {
|
||||
user_id: Some(member.user.id.0.to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
.into(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(database_remove_result) = delete_result {
|
||||
Some(database_remove_result)
|
||||
} else if let Some(find_result) = delete_result.unwrap() {
|
||||
let taken_roles = find_result
|
||||
.taken_roles
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|r| RoleId::from(r.parse::<u64>().unwrap()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Err(add_role_result) = member.add_roles(&ctx.http, &taken_roles).await {
|
||||
Some(Error::from(add_role_result))
|
||||
} else if let Err(remove_result) = member.remove_role(ctx.http, mute_role_id).await {
|
||||
Some(Error::from(remove_result))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn respond_mute_command(
|
||||
ctx: &Context<'_>,
|
||||
moderation: ModerationKind,
|
||||
user: &serenity::User,
|
||||
embed_color: i32,
|
||||
) -> Result<(), Error> {
|
||||
let tag = user.tag();
|
||||
let image = user
|
||||
.avatar_url()
|
||||
.unwrap_or_else(|| user.default_avatar_url());
|
||||
|
||||
ctx.send(|f| {
|
||||
f.embed(|f| {
|
||||
match moderation {
|
||||
ModerationKind::Mute(reason, expires, error) => match error {
|
||||
Some(err) => f.title(format!("Failed to mute {}", tag)).field(
|
||||
"Exception",
|
||||
err.to_string(),
|
||||
false,
|
||||
),
|
||||
None => f.title(format!("Muted {}", tag)),
|
||||
}
|
||||
.field("Reason", reason, false)
|
||||
.field("Expires", expires, false),
|
||||
ModerationKind::Unmute(error) => match error {
|
||||
Some(err) => f.title(format!("Failed to unmute {}", tag)).field(
|
||||
"Exception",
|
||||
err.to_string(),
|
||||
false,
|
||||
),
|
||||
None => f.title(format!("Unmuted {}", tag)),
|
||||
},
|
||||
}
|
||||
.color(embed_color)
|
||||
.thumbnail(image)
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user