feat: moderation commands

This commit is contained in:
oSumAtrIX 2022-08-20 15:44:45 +02:00
parent 9407f1b37e
commit 54171ba2ce
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
27 changed files with 1417 additions and 192 deletions

32
.cargo/config.toml Normal file
View 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 = '...'

View File

@ -1,2 +1,4 @@
# The Discord authorization token for the bot, requires the MESSAGE_CONTENT intent # 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
View File

@ -8,6 +8,17 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 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]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.18" version = "0.7.18"
@ -80,6 +91,25 @@ dependencies = [
"generic-array", "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]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.10.0" version = "3.10.0"
@ -153,14 +183,38 @@ dependencies = [
"typenum", "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]] [[package]]
name = "darling" name = "darling"
version = "0.14.1" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02"
dependencies = [ dependencies = [
"darling_core", "darling_core 0.14.1",
"darling_macro", "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]] [[package]]
@ -177,13 +231,24 @@ dependencies = [
"syn", "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]] [[package]]
name = "darling_macro" name = "darling_macro"
version = "0.14.1" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5"
dependencies = [ dependencies = [
"darling_core", "darling_core 0.14.1",
"quote", "quote",
"syn", "syn",
] ]
@ -201,6 +266,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "data-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
[[package]] [[package]]
name = "decancer" name = "decancer"
version = "1.4.1" version = "1.4.1"
@ -226,6 +297,7 @@ checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
dependencies = [ dependencies = [
"block-buffer", "block-buffer",
"crypto-common", "crypto-common",
"subtle",
] ]
[[package]] [[package]]
@ -263,6 +335,18 @@ dependencies = [
"cfg-if", "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]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.24" version = "1.0.24"
@ -319,12 +403,34 @@ version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 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]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.21" version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" 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]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.21" version = "0.3.21"
@ -346,6 +452,7 @@ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
"futures-io", "futures-io",
"futures-macro",
"futures-sink", "futures-sink",
"futures-task", "futures-task",
"memchr", "memchr",
@ -400,6 +507,12 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.1.19" version = "0.1.19"
@ -409,6 +522,32 @@ dependencies = [
"libc", "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]] [[package]]
name = "http" name = "http"
version = "0.2.8" version = "0.2.8"
@ -507,6 +646,18 @@ dependencies = [
"hashbrown", "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]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.5.0" version = "2.5.0"
@ -540,6 +691,12 @@ version = "0.2.127"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b" checksum = "505e71a4706fa491e9b1b55f51b95d4037d0821ee40131190475f692b35b009b"
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.7" version = "0.4.7"
@ -559,12 +716,36 @@ dependencies = [
"cfg-if", "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]] [[package]]
name = "matches" name = "matches"
version = "0.1.9" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 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]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.5.0"
@ -608,6 +789,52 @@ dependencies = [
"windows-sys", "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]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.45" version = "0.1.45"
@ -661,6 +888,16 @@ dependencies = [
"num-traits", "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]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.1" version = "0.12.1"
@ -684,6 +921,15 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "pbkdf2"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7"
dependencies = [
"digest",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@ -727,7 +973,7 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ff861b6a52ec47bc54eb17424c025feeb040e82836036276c25dda045a8a0c" checksum = "52ff861b6a52ec47bc54eb17424c025feeb040e82836036276c25dda045a8a0c"
dependencies = [ dependencies = [
"darling", "darling 0.14.1",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn", "syn",
@ -748,6 +994,12 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.21" version = "1.0.21"
@ -849,7 +1101,7 @@ dependencies = [
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rustls", "rustls",
"rustls-pemfile", "rustls-pemfile 1.0.1",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
@ -862,17 +1114,29 @@ dependencies = [
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"webpki-roots", "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]] [[package]]
name = "revanced-discord-bot" name = "revanced-discord-bot"
version = "1.1.0" version = "2.2.1"
dependencies = [ dependencies = [
"bson",
"chrono", "chrono",
"decancer", "decancer",
"dirs", "dirs",
"dotenv", "dotenv",
"mongodb",
"poise", "poise",
"regex", "regex",
"serde", "serde",
@ -898,6 +1162,25 @@ dependencies = [
"winapi", "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]] [[package]]
name = "rustls" name = "rustls"
version = "0.20.6" version = "0.20.6"
@ -910,6 +1193,15 @@ dependencies = [
"webpki", "webpki",
] ]
[[package]]
name = "rustls-pemfile"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360"
dependencies = [
"base64",
]
[[package]] [[package]]
name = "rustls-pemfile" name = "rustls-pemfile"
version = "1.0.1" version = "1.0.1"
@ -947,6 +1239,21 @@ dependencies = [
"untrusted", "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]] [[package]]
name = "serde" name = "serde"
version = "1.0.142" version = "1.0.142"
@ -966,6 +1273,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_bytes"
version = "0.11.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.142" version = "1.0.142"
@ -983,6 +1299,7 @@ version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7" checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
dependencies = [ dependencies = [
"indexmap",
"itoa", "itoa",
"ryu", "ryu",
"serde", "serde",
@ -1010,6 +1327,28 @@ dependencies = [
"serde", "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]] [[package]]
name = "serenity" name = "serenity"
version = "0.11.5" version = "0.11.5"
@ -1053,6 +1392,17 @@ dependencies = [
"digest", "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]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.4" version = "0.1.4"
@ -1093,12 +1443,28 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" 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]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "subtle"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.99" version = "1.0.99"
@ -1110,6 +1476,12 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "take_mut"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.32" version = "1.0.32"
@ -1161,8 +1533,15 @@ dependencies = [
"libc", "libc",
"num_threads", "num_threads",
"serde", "serde",
"time-macros",
] ]
[[package]]
name = "time-macros"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@ -1298,6 +1677,51 @@ dependencies = [
"tracing-log", "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]] [[package]]
name = "try-lock" name = "try-lock"
version = "0.2.3" version = "0.2.3"
@ -1325,6 +1749,17 @@ dependencies = [
"webpki", "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]] [[package]]
name = "typemap_rev" name = "typemap_rev"
version = "0.1.5" version = "0.1.5"
@ -1392,6 +1827,25 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 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]] [[package]]
name = "valuable" name = "valuable"
version = "0.1.0" version = "0.1.0"
@ -1521,6 +1975,12 @@ dependencies = [
"webpki", "webpki",
] ]
[[package]]
name = "widestring"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17882f045410753661207383517a6f62ec3dbeb6a4ed2acce01f0728238d1983"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@ -1586,6 +2046,15 @@ version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
[[package]]
name = "winreg"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.10.1" version = "0.10.1"

View File

@ -6,7 +6,7 @@ keywords = ["ReVanced"]
license = "GPL-3.0" license = "GPL-3.0"
name = "revanced-discord-bot" name = "revanced-discord-bot"
repository = "https://github.com/revanced/revanced-discord-bot" repository = "https://github.com/revanced/revanced-discord-bot"
version = "1.1.0" version = "2.2.1"
edition = "2021" edition = "2021"
[profile.release] [profile.release]
@ -17,8 +17,10 @@ codegen-units = 1
panic = "abort" panic = "abort"
[dependencies] [dependencies]
poise = "0.3.0" bson = "2.4"
decancer = "1.4.1" mongodb = "2.3"
poise = "0.3"
decancer = "1.4"
tokio = { version = "1.20.1", features = ["rt-multi-thread"] } tokio = { version = "1.20.1", features = ["rt-multi-thread"] }
dotenv = "0.15" dotenv = "0.15"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
@ -26,6 +28,6 @@ serde_json = "1.0"
regex = "1.0" regex = "1.0"
serde_regex = "1.1" serde_regex = "1.1"
chrono = "0.4" chrono = "0.4"
dirs = "4.0.0" dirs = "4.0"
tracing = { version = "0.1", features = ["max_level_debug", "release_max_level_info"] } tracing = { version = "0.1", features = ["max_level_debug", "release_max_level_info"] }
tracing-subscriber = "0.3" tracing-subscriber = "0.3"

View File

@ -1,7 +1,11 @@
{ {
"$schema": "./configuration.schema.json", "$schema": "./configuration.schema.json",
"general": { "general": {
"embed_color": 0 "embed_color": 0,
"mute": {
"role": 0,
"take": [0]
}
}, },
"administrators": { "administrators": {
"roles": [0], "roles": [0],

View File

@ -1,6 +1,10 @@
{ {
"general": { "general": {
"embed_color": 14908858 "embed_color": 14908858,
"mute": {
"role": 953984696491061289,
"take": [996121272897519687]
}
}, },
"administrators": { "administrators": {
"roles": [973886585294704640], "roles": [973886585294704640],

View File

@ -10,6 +10,24 @@
"properties": { "properties": {
"embed_color": { "embed_color": {
"$ref": "#/$defs/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
}
}
} }
} }
}, },

View File

@ -1,18 +1,18 @@
use tracing::debug; use tracing::debug;
use crate::utils::load_configuration; use crate::utils::bot::load_configuration;
use crate::{Context, Error}; use crate::{Context, Error};
#[poise::command(slash_command, prefix_command)] #[poise::command(slash_command)]
pub async fn reload(ctx: Context<'_>) -> Result<(), Error> { pub async fn reload(ctx: Context<'_>) -> Result<(), Error> {
// Update the configuration // Update the configuration
let configuration = load_configuration(); let configuration = load_configuration();
// Use the embed color from the updated configuration // Use the embed color from the updated configuration
let embed_color = configuration.general.embed_color; let embed_color = configuration.general.embed_color;
// Also save the new configuration to the user data // 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| { ctx.send(|f| {
f.ephemeral(true).embed(|f| { f.ephemeral(true).embed(|f| {
@ -25,18 +25,31 @@ pub async fn reload(ctx: Context<'_>) -> Result<(), Error> {
Ok(()) Ok(())
} }
#[poise::command(slash_command, prefix_command)] #[poise::command(slash_command)]
pub async fn stop(ctx: Context<'_>) -> Result<(), Error> { 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| { ctx.send(|f| {
f.ephemeral(true) f.ephemeral(true)
.embed(|f| f.description("Stopped the bot.").color(color)) .embed(|f| f.description("Stopped the bot.").color(color))
}) })
.await?; .await?;
ctx.discord().shard.shutdown_clean(); ctx.framework()
.shard_manager()
.lock()
.await
.shutdown_all()
.await;
Ok(()) Ok(())
} }

View File

@ -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
View 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
View File

@ -0,0 +1,2 @@
pub mod database;
pub mod model;

32
src/db/model.rs Normal file
View 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")
}
}

View File

@ -1,5 +1,6 @@
use super::*; use super::*;
use crate::utils::decancer::cure;
pub async fn guild_member_addition(ctx: &serenity::Context, new_member: &serenity::Member) { 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;
} }

View File

@ -1,9 +1,10 @@
use super::*; use super::*;
use crate::utils::decancer::cure;
pub async fn guild_member_update( pub async fn guild_member_update(
ctx: &serenity::Context, ctx: &serenity::Context,
_old_if_available: &Option<serenity::Member>, _old_if_available: &Option<serenity::Member>,
new: &serenity::Member, new: &serenity::Member,
) { ) {
crate::utils::cure(ctx, new).await; cure(ctx, new).await;
} }

View File

@ -3,6 +3,7 @@ use regex::Regex;
use tracing::debug; use tracing::debug;
use super::*; use super::*;
use crate::utils::bot::get_data_lock;
fn contains_match(regex: &[Regex], text: &str) -> bool { fn contains_match(regex: &[Regex], text: &str) -> bool {
regex.iter().any(|r| r.is_match(text)) 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; return;
} }
if let Some(message_response) = get_configuration_lock(ctx) if let Some(message_response) = get_data_lock(ctx)
.await .await
.read() .read()
.await .await
.configuration
.read()
.await
.message_responses .message_responses
.iter() .iter()
.find(|&response| { .find(|&response| {

View File

@ -2,24 +2,14 @@ use std::sync::Arc;
use poise::serenity_prelude::{self as serenity, Mutex, RwLock, ShardManager, UserId}; use poise::serenity_prelude::{self as serenity, Mutex, RwLock, ShardManager, UserId};
use crate::model::application::Configuration; use crate::{Data, Error};
use crate::Error;
mod guild_member_addition; mod guild_member_addition;
mod guild_member_update; mod guild_member_update;
mod message_create; mod message_create;
mod ready;
mod thread_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> { pub struct Handler<T> {
options: poise::FrameworkOptions<T, Error>, options: poise::FrameworkOptions<T, Error>,
data: T, data: T,
@ -55,9 +45,11 @@ impl<T: Send + Sync> Handler<T> {
// Manually dispatch events from serenity to poise // Manually dispatch events from serenity to poise
#[serenity::async_trait] #[serenity::async_trait]
impl serenity::EventHandler for Handler<Arc<RwLock<Configuration>>> { impl serenity::EventHandler for Handler<Arc<RwLock<Data>>> {
async fn ready(&self, _ctx: serenity::Context, ready: serenity::Ready) { async fn ready(&self, ctx: serenity::Context, ready: serenity::Ready) {
*self.bot_id.write().await = Some(ready.user.id); *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) { async fn message(&self, ctx: serenity::Context, new_message: serenity::Message) {

59
src/events/ready.rs Normal file
View 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);
}
}
}

View File

@ -1,6 +1,7 @@
use tracing::{debug, error}; use tracing::{debug, error};
use super::*; use super::*;
use crate::utils::bot::get_data_lock;
pub async fn thread_create(ctx: &serenity::Context, thread: &serenity::GuildChannel) { pub async fn thread_create(ctx: &serenity::Context, thread: &serenity::GuildChannel) {
if thread.member.is_some() { if thread.member.is_some() {
@ -10,8 +11,14 @@ pub async fn thread_create(ctx: &serenity::Context, thread: &serenity::GuildChan
debug!("Thread created: {:?}", thread); debug!("Thread created: {:?}", thread);
let configuration_lock = get_configuration_lock(ctx).await; let data_lock = get_data_lock(ctx).await;
let thread_introductions = &configuration_lock.read().await.thread_introductions; 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| { if let Some(introducer) = thread_introductions.iter().find(|introducer| {
introducer introducer

View File

@ -1,24 +1,35 @@
use std::collections::HashMap;
use std::env; use std::env;
use std::sync::Arc; use std::sync::Arc;
use commands::configuration; use commands::{configuration, moderation};
use db::database::Database;
use events::Handler; use events::Handler;
use poise::serenity_prelude::{self as serenity, RwLock}; use poise::serenity_prelude::{self as serenity, RwLock, UserId};
use utils::load_configuration; use tokio::task::JoinHandle;
use tracing::{error, trace};
use utils::bot::load_configuration;
use crate::model::application::Configuration; use crate::model::application::Configuration;
mod commands; mod commands;
mod db;
mod events; mod events;
mod logger; mod logger;
mod model; mod model;
mod utils; mod utils;
type Error = Box<dyn std::error::Error + Send + Sync>; 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 { impl serenity::TypeMapKey for Data {
type Value = Arc<RwLock<Configuration>>; 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] #[tokio::main]
@ -34,13 +45,40 @@ async fn main() {
configuration::register(), configuration::register(),
configuration::reload(), configuration::reload(),
configuration::stop(), configuration::stop(),
moderation::mute(),
moderation::unmute(),
moderation::purge(),
]; ];
poise::set_qualified_names(&mut commands); 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( let handler = Arc::new(Handler::new(
poise::FrameworkOptions { poise::FrameworkOptions {
owners,
commands, commands,
on_error: |error| { on_error: |error| {
Box::pin(async { Box::pin(async {
@ -52,7 +90,9 @@ async fn main() {
command_check: Some(|ctx| { command_check: Some(|ctx| {
Box::pin(async move { Box::pin(async move {
if let Some(member) = ctx.author_member().await { 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 if !(administrators
.users .users
@ -69,6 +109,25 @@ async fn main() {
.any(|member_role| member_role.0 == role_id) .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 return Ok(false); // Not an administrator, don't allow command execution
} }
} }
@ -83,12 +142,12 @@ async fn main() {
}, },
..Default::default() ..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( let mut client = serenity::Client::builder(
env::var("DISCORD_AUTHORIZATION_TOKEN") 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::non_privileged()
| serenity::GatewayIntents::MESSAGE_CONTENT | serenity::GatewayIntents::MESSAGE_CONTENT
| serenity::GatewayIntents::GUILD_MEMBERS, | serenity::GatewayIntents::GUILD_MEMBERS,
@ -97,11 +156,7 @@ async fn main() {
.await .await
.unwrap(); .unwrap();
client client.data.write().await.insert::<Data>(data);
.data
.write()
.await
.insert::<Configuration>(configuration);
handler handler
.set_shard_manager(client.shard_manager.clone()) .set_shard_manager(client.shard_manager.clone())

View File

@ -1,8 +1,6 @@
use std::{ use std::fs::{self, File};
fs::{self, File}, use std::io::{Read, Result, Write};
io::{Read, Result, Write}, use std::path::Path;
path::Path,
};
use dirs::config_dir; use dirs::config_dir;
use regex::Regex; use regex::Regex;
@ -22,7 +20,10 @@ impl Configuration {
fn save(&self) -> Result<()> { fn save(&self) -> Result<()> {
let sys_config_dir = config_dir().expect("find config dir"); let sys_config_dir = config_dir().expect("find config dir");
fs::create_dir_all(format!("{}/revanced-discord-bot", sys_config_dir.to_string_lossy())) fs::create_dir_all(format!(
"{}/revanced-discord-bot",
sys_config_dir.to_string_lossy()
))
.expect("create config dir"); .expect("create config dir");
let mut file = File::create(CONFIG_PATH)?; let mut file = File::create(CONFIG_PATH)?;
@ -33,8 +34,10 @@ impl Configuration {
pub fn load() -> Result<Configuration> { pub fn load() -> Result<Configuration> {
let sys_config_dir = config_dir().expect("Can not find the configuration directory."); let sys_config_dir = config_dir().expect("Can not find the configuration directory.");
let sys_config = let sys_config = format!(
format!("{}/revanced-discord-bot/{CONFIG_PATH}", sys_config_dir.to_string_lossy()); "{}/revanced-discord-bot/{CONFIG_PATH}",
sys_config_dir.to_string_lossy()
);
// config file in current dir // config file in current dir
let mut file = if Path::new(CONFIG_PATH).exists() { let mut file = if Path::new(CONFIG_PATH).exists() {
@ -62,8 +65,14 @@ impl Configuration {
#[derive(Default, Serialize, Deserialize)] #[derive(Default, Serialize, Deserialize)]
pub struct General { pub struct General {
pub embed_color: i32, 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)] #[derive(Default, Serialize, Deserialize)]
pub struct Administrators { pub struct Administrators {
pub roles: Vec<u64>, pub roles: Vec<u64>,

View File

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