v1.0
This commit is contained in:
parent
6e1e91936f
commit
008ff5b7cf
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
/target
|
||||
.env
|
||||
.env
|
||||
config.toml
|
507
Cargo.lock
generated
507
Cargo.lock
generated
@ -88,6 +88,12 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.4.0"
|
||||
@ -229,11 +235,34 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz-build",
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz-build"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7"
|
||||
dependencies = [
|
||||
"parse-zoneinfo",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "command_attr"
|
||||
version = "0.5.3"
|
||||
@ -563,6 +592,21 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
@ -732,6 +776,25 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.1.0",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
@ -835,6 +898,29 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"http-body 1.0.1",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.9.5"
|
||||
@ -857,9 +943,9 @@ dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"h2 0.3.26",
|
||||
"http 0.2.12",
|
||||
"http-body",
|
||||
"http-body 0.4.6",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
@ -871,6 +957,26 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2 0.4.7",
|
||||
"http 1.1.0",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.24.2"
|
||||
@ -879,12 +985,64 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"hyper",
|
||||
"hyper 0.14.31",
|
||||
"rustls 0.21.12",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"hyper 1.5.1",
|
||||
"hyper-util",
|
||||
"rustls 0.23.18",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.0",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http-body-util",
|
||||
"hyper 1.5.1",
|
||||
"hyper-util",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"http-body 1.0.1",
|
||||
"hyper 1.5.1",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
@ -1224,6 +1382,23 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
@ -1312,6 +1487,50 @@ version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.89",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
@ -1347,6 +1566,15 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-zoneinfo"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
@ -1368,6 +1596,44 @@ version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.15"
|
||||
@ -1559,11 +1825,11 @@ dependencies = [
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"h2 0.3.26",
|
||||
"http 0.2.12",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.31",
|
||||
"hyper-rustls 0.24.2",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
@ -1577,8 +1843,8 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"system-configuration",
|
||||
"sync_wrapper 0.1.2",
|
||||
"system-configuration 0.5.1",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
"tokio-util",
|
||||
@ -1592,6 +1858,49 @@ dependencies = [
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.4.7",
|
||||
"http 1.1.0",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.5.1",
|
||||
"hyper-rustls 0.27.3",
|
||||
"hyper-tls",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pemfile 2.2.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.2",
|
||||
"system-configuration 0.6.1",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.8"
|
||||
@ -1746,6 +2055,15 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@ -1772,6 +2090,29 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa39c7303dc58b5543c94d22c1766b0d31f2ee58306363ea622b10bbc075eaa2"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.23"
|
||||
@ -1822,6 +2163,15 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
@ -1855,7 +2205,7 @@ dependencies = [
|
||||
"mime_guess",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"reqwest",
|
||||
"reqwest 0.11.27",
|
||||
"secrecy",
|
||||
"serde",
|
||||
"serde_cow",
|
||||
@ -1931,16 +2281,27 @@ dependencies = [
|
||||
name = "sillycord-bot"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"dotenv",
|
||||
"poise",
|
||||
"reqwest 0.12.9",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serenity",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"toml",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||
|
||||
[[package]]
|
||||
name = "skeptic"
|
||||
version = "0.13.7"
|
||||
@ -2273,6 +2634,15 @@ version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.1"
|
||||
@ -2292,7 +2662,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"system-configuration-sys",
|
||||
"system-configuration-sys 0.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"core-foundation",
|
||||
"system-configuration-sys 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2305,6 +2686,16 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tagptr"
|
||||
version = "0.2.0"
|
||||
@ -2439,6 +2830,16 @@ dependencies = [
|
||||
"syn 2.0.89",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.24.1"
|
||||
@ -2460,6 +2861,17 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
|
||||
dependencies = [
|
||||
"rustls 0.23.18",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.16"
|
||||
@ -2500,6 +2912,40 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.3"
|
||||
@ -2923,6 +3369,36 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
@ -3071,6 +3547,15 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.50.0"
|
||||
|
@ -11,4 +11,9 @@ sqlx = { version = "0.8", features = [ "runtime-tokio", "tls-rustls-ring", "mysq
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
serde = { version = "1.0.215", features = ["derive"] }
|
||||
toml = "0.8.19"
|
||||
chrono = "0.4.38"
|
||||
chrono-tz = "0.10.0"
|
||||
reqwest = "0.12.9"
|
||||
serde_json = "1.0.133"
|
||||
|
9
migrations/20241125045124_init.sql
Normal file
9
migrations/20241125045124_init.sql
Normal file
@ -0,0 +1,9 @@
|
||||
-- Create basic users table for fun stuff around the bot
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
discord_id VARCHAR(255) NOT NULL UNIQUE,
|
||||
actions_allowed TINYINT DEFAULT 1,
|
||||
about TEXT,
|
||||
pronouns VARCHAR(255)
|
||||
);
|
||||
|
159
src/commands/action.rs
Normal file
159
src/commands/action.rs
Normal file
@ -0,0 +1,159 @@
|
||||
use crate::{utils, Context, Error};
|
||||
use poise::CreateReply;
|
||||
use serenity::all::{CreateAllowedMentions, CreateAttachment, Message};
|
||||
use serenity::builder::CreateMessage;
|
||||
use serenity::model::prelude::UserId;
|
||||
use serenity::prelude::Mentionable;
|
||||
|
||||
async fn check_if_allowed(ctx: Context<'_>, message: Message) -> Result<bool, Error> {
|
||||
let user = ctx
|
||||
.data()
|
||||
.database_controller
|
||||
.get_user_by_discord_id(message.author.id.into())
|
||||
.await?;
|
||||
|
||||
if user.is_none() {
|
||||
ctx.data()
|
||||
.database_controller
|
||||
.create_user(message.author.id.into())
|
||||
.await?;
|
||||
}
|
||||
|
||||
let user = ctx
|
||||
.data()
|
||||
.database_controller
|
||||
.get_user_by_discord_id(message.author.id.into())
|
||||
.await?;
|
||||
|
||||
Ok(user.unwrap().actions_allowed)
|
||||
}
|
||||
|
||||
#[poise::command(context_menu_command = "Hug User")]
|
||||
pub async fn use_action_hug(
|
||||
ctx: Context<'_>,
|
||||
#[description = "The target message to use the action with"] message: Message,
|
||||
) -> Result<(), Error> {
|
||||
ctx.defer().await?;
|
||||
|
||||
let allowed = check_if_allowed(ctx, message.clone()).await?;
|
||||
|
||||
if !allowed {
|
||||
ctx.send(CreateReply::default().content(
|
||||
":x: Sorry, either that user has disabled actions or has no profile to check against",
|
||||
).ephemeral(true))
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let action_img = utils::get_random_action_image("hug".to_string()).await;
|
||||
|
||||
// Match the result of the action image
|
||||
match action_img {
|
||||
Ok(img) => {
|
||||
let user = UserId::new(ctx.author().id.into());
|
||||
let target = UserId::new(message.clone().author.id.into());
|
||||
let builder = CreateReply::default()
|
||||
.content(format!("{} hugs {}", user.mention(), target.mention()))
|
||||
.attachment(CreateAttachment::url(ctx.http(), &img).await?)
|
||||
.allowed_mentions(CreateAllowedMentions::default().users(vec![user, target]));
|
||||
|
||||
ctx.send(builder).await?;
|
||||
}
|
||||
Err(_) => {
|
||||
ctx.send(
|
||||
CreateReply::default()
|
||||
.content(":x: Something went wrong while fetching the action image")
|
||||
.ephemeral(true),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[poise::command(context_menu_command = "Kiss User")]
|
||||
pub async fn use_action_kiss(
|
||||
ctx: Context<'_>,
|
||||
#[description = "The target message to use the action with"] message: Message,
|
||||
) -> Result<(), Error> {
|
||||
ctx.defer().await?;
|
||||
let allowed = check_if_allowed(ctx, message.clone()).await?;
|
||||
|
||||
if !allowed {
|
||||
ctx.send(CreateReply::default().content(
|
||||
":x: Sorry, either that user has disabled actions or has no profile to check against",
|
||||
).ephemeral(true))
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let action_img = utils::get_random_action_image("kiss".to_string()).await;
|
||||
|
||||
// Match the result of the action image
|
||||
match action_img {
|
||||
Ok(img) => {
|
||||
let user = UserId::new(ctx.author().id.into());
|
||||
let target = UserId::new(message.author.id.into());
|
||||
let builder = CreateReply::default()
|
||||
.content(format!("{} kisses {}", user.mention(), target.mention()))
|
||||
.attachment(CreateAttachment::url(ctx.http(), &img).await?)
|
||||
.allowed_mentions(CreateAllowedMentions::default().users(vec![user, target]));
|
||||
|
||||
ctx.send(builder).await?;
|
||||
}
|
||||
Err(_) => {
|
||||
ctx.send(
|
||||
CreateReply::default()
|
||||
.content(":x: Something went wrong while fetching the action image")
|
||||
.ephemeral(true),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[poise::command(context_menu_command = "Pat User")]
|
||||
pub async fn use_action_pat(
|
||||
ctx: Context<'_>,
|
||||
#[description = "The target message to use the action with"] message: Message,
|
||||
) -> Result<(), Error> {
|
||||
ctx.defer().await?;
|
||||
let allowed = check_if_allowed(ctx, message.clone()).await?;
|
||||
|
||||
if !allowed {
|
||||
ctx.send(CreateReply::default().content(
|
||||
":x: Sorry, either that user has disabled actions or has no profile to check against",
|
||||
).ephemeral(true))
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let action_img = utils::get_random_action_image("pat".to_string()).await;
|
||||
|
||||
// Match the result of the action image
|
||||
match action_img {
|
||||
Ok(img) => {
|
||||
let user = UserId::new(ctx.author().id.into());
|
||||
let target = UserId::new(message.author.id.into());
|
||||
let builder = CreateReply::default()
|
||||
.content(format!("{} pats {}", user.mention(), target.mention()))
|
||||
.attachment(CreateAttachment::url(ctx.http(), &img).await?)
|
||||
.allowed_mentions(CreateAllowedMentions::default().users(vec![user, target]));
|
||||
|
||||
ctx.send(builder).await?;
|
||||
}
|
||||
Err(_) => {
|
||||
ctx.send(
|
||||
CreateReply::default()
|
||||
.content(":x: Something went wrong while fetching the action image")
|
||||
.ephemeral(true),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
34
src/commands/cta.rs
Normal file
34
src/commands/cta.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use poise::CreateReply;
|
||||
use serenity::{all::CreateEmbed, model::colour};
|
||||
use reqwest::Client;
|
||||
use serde_json::Value;
|
||||
use crate::{Context, Error};
|
||||
|
||||
/// Returns a random cat image, along with a random cat fact.
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn cta(ctx: Context<'_>) -> Result<(), Error> {
|
||||
let cat_fact_res = Client::new()
|
||||
.get("https://catfact.ninja/fact")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let cat_fact: Value = serde_json::from_str(&cat_fact_res.text().await?)?;
|
||||
let cat_fact = cat_fact["fact"].as_str().unwrap();
|
||||
|
||||
let cat_image_res = Client::new()
|
||||
.get("https://api.thecatapi.com/v1/images/search")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let cat_image: Value = serde_json::from_str(&cat_image_res.text().await?)?;
|
||||
let cat_image = cat_image[0]["url"].as_str().unwrap();
|
||||
|
||||
let embed = CreateEmbed::default()
|
||||
.title("Random Cat")
|
||||
.description(cat_fact)
|
||||
.image(cat_image)
|
||||
.color(colour::Colour::from_rgb(0, 255, 255));
|
||||
|
||||
ctx.send(CreateReply::default().embed(embed)).await?;
|
||||
Ok(())
|
||||
}
|
25
src/commands/dog.rs
Normal file
25
src/commands/dog.rs
Normal file
@ -0,0 +1,25 @@
|
||||
use crate::{Context, Error};
|
||||
use poise::CreateReply;
|
||||
use reqwest::Client;
|
||||
use serde_json::Value;
|
||||
use serenity::{all::CreateEmbed, model::colour};
|
||||
|
||||
/// Returns a random dog image.
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn dog(ctx: Context<'_>) -> Result<(), Error> {
|
||||
let dog_res = Client::new()
|
||||
.get("https://dog.ceo/api/breeds/image/random")
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let dog: Value = serde_json::from_str(&dog_res.text().await?)?;
|
||||
let dog = dog["message"].as_str().unwrap();
|
||||
|
||||
let embed = CreateEmbed::default()
|
||||
.title("Random Dog")
|
||||
.image(dog)
|
||||
.color(colour::Colour::from_rgb(0, 255, 255));
|
||||
|
||||
ctx.send(CreateReply::default().embed(embed)).await?;
|
||||
Ok(())
|
||||
}
|
@ -1 +1,6 @@
|
||||
pub mod ping;
|
||||
pub mod vouch;
|
||||
pub mod cta;
|
||||
pub mod dog;
|
||||
pub mod profile;
|
||||
pub mod action;
|
@ -1,16 +1,32 @@
|
||||
use poise::CreateReply;
|
||||
use serenity::{all::CreateEmbed, model::colour};
|
||||
|
||||
use crate::{Context, Error};
|
||||
|
||||
/// Pong? Returns the ping of the bot and process uptime
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn ping(ctx: Context<'_>) -> Result<(), Error> {
|
||||
let start = std::time::Instant::now();
|
||||
let msg = ctx.say("Pong?").await?;
|
||||
|
||||
let latency = start.elapsed();
|
||||
msg.edit(
|
||||
ctx,
|
||||
poise::CreateReply::default().content(format!("Pong! Latency: {:?}", latency)),
|
||||
)
|
||||
.await?;
|
||||
// Create pining embed
|
||||
let mut embed = CreateEmbed::default()
|
||||
.title("Ping?")
|
||||
.description("Pinging...")
|
||||
.color(colour::Colour::PURPLE);
|
||||
|
||||
let msg = ctx.send(CreateReply::default().embed(embed)).await?;
|
||||
|
||||
let ping = start.elapsed().as_millis();
|
||||
embed = CreateEmbed::default()
|
||||
.title("Pong!")
|
||||
.description(format!(
|
||||
"Pong! Took {}ms!\nProcess uptime: {:?}",
|
||||
ping,
|
||||
// Round to 2 decimal places
|
||||
(ctx.data().uptime.elapsed().as_secs_f64() * 100.0).floor() / 100.0
|
||||
))
|
||||
.color(colour::Colour::DARK_GREEN);
|
||||
|
||||
msg.edit(ctx, CreateReply::default().embed(embed)).await?; // Edit the message with the ping
|
||||
Ok(())
|
||||
}
|
||||
|
142
src/commands/profile.rs
Normal file
142
src/commands/profile.rs
Normal file
@ -0,0 +1,142 @@
|
||||
use crate::structs::user::User as UserStruct;
|
||||
use crate::{Context, Error};
|
||||
use poise::CreateReply;
|
||||
use serenity::all::{Colour, CreateEmbed, User};
|
||||
|
||||
/// Commands related to profiles in the bot
|
||||
#[poise::command(
|
||||
slash_command,
|
||||
subcommands("view", "edit"),
|
||||
check = "ensure_profile_is_setup"
|
||||
)]
|
||||
pub async fn profiles(_ctx: Context<'_>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Ensure the user has a profile setup
|
||||
/// If the user doesn't have a profile, create one
|
||||
async fn ensure_profile_is_setup(ctx: Context<'_>) -> Result<bool, Error> {
|
||||
let profile = ctx
|
||||
.data()
|
||||
.database_controller
|
||||
.get_user_by_discord_id(ctx.author().id.into())
|
||||
.await?;
|
||||
|
||||
if profile.is_none() {
|
||||
ctx.data()
|
||||
.database_controller
|
||||
.create_user(ctx.author().id.into())
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// View a user's profile
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn view(
|
||||
_ctx: Context<'_>,
|
||||
#[description = "The user to view the profile of, defaults to yourself"] user: Option<User>,
|
||||
) -> Result<(), Error> {
|
||||
// Get the profile of the provided user, or the user who ran the command
|
||||
let target_user = user.unwrap_or_else(|| _ctx.author().clone());
|
||||
|
||||
// Get the profile of the target user
|
||||
let profile = _ctx
|
||||
.data()
|
||||
.database_controller
|
||||
.get_user_by_discord_id(target_user.id.into())
|
||||
.await?;
|
||||
|
||||
match profile {
|
||||
Some(profile) => {
|
||||
let profile_embed = CreateEmbed::default()
|
||||
.title(format!("Profile of {}", target_user.tag()))
|
||||
.description(format!(
|
||||
"About: {}\nPronouns: {}\nActions Allowed: {}",
|
||||
profile.about.unwrap_or("No about section".to_string()),
|
||||
profile.pronouns.unwrap_or("No pronouns set".to_string()),
|
||||
if profile.actions_allowed { "Yes" } else { "No" }
|
||||
))
|
||||
.color(Colour::FABLED_PINK);
|
||||
|
||||
_ctx.send(CreateReply::default().embed(profile_embed))
|
||||
.await?;
|
||||
}
|
||||
None => {
|
||||
// Send a emphul messagrespond(formate if the user doesn't have a profile
|
||||
_ctx.send(
|
||||
CreateReply::default()
|
||||
.content(":x: User has no profile")
|
||||
.ephemeral(true),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Edit your profile
|
||||
#[poise::command(slash_command)]
|
||||
pub async fn edit(
|
||||
_ctx: Context<'_>,
|
||||
#[description = "Your about section"] about: Option<String>,
|
||||
#[description = "Your pronouns"] pronouns: Option<String>,
|
||||
#[description = "Whether you want to allow actions to be performed on you"]
|
||||
actions_allowed: Option<bool>,
|
||||
) -> Result<(), Error> {
|
||||
// If no options are provided, send a help message
|
||||
if about.is_none() && pronouns.is_none() && actions_allowed.is_none() {
|
||||
_ctx.send(
|
||||
CreateReply::default()
|
||||
.content("Please provide at least one option to edit")
|
||||
.ephemeral(true),
|
||||
)
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Get the profile of the user who ran the command
|
||||
let profile = _ctx
|
||||
.data()
|
||||
.database_controller
|
||||
.get_user_by_discord_id(_ctx.author().id.into())
|
||||
.await?;
|
||||
|
||||
// If the user doesn't have a profile, create one
|
||||
let profile = match profile {
|
||||
Some(profile) => profile,
|
||||
None => {
|
||||
_ctx.data()
|
||||
.database_controller
|
||||
.create_user(_ctx.author().id.into())
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
// Generate a user struct with the updated values
|
||||
let updated_profile = UserStruct {
|
||||
id: profile.id,
|
||||
discord_id: profile.discord_id,
|
||||
about,
|
||||
pronouns,
|
||||
actions_allowed: actions_allowed.unwrap_or(profile.actions_allowed),
|
||||
};
|
||||
|
||||
// Update the user's profile
|
||||
_ctx.data()
|
||||
.database_controller
|
||||
.update_user(updated_profile)
|
||||
.await?;
|
||||
|
||||
// Send a success message
|
||||
_ctx.send(
|
||||
CreateReply::default()
|
||||
.content(":white_check_mark: Profile updated successfully!")
|
||||
.ephemeral(true),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
303
src/commands/vouch.rs
Normal file
303
src/commands/vouch.rs
Normal file
@ -0,0 +1,303 @@
|
||||
use crate::{structs::vouch::Vouch, Context, Error};
|
||||
use serenity::all::{
|
||||
Colour, CreateAllowedMentions, CreateEmbed, CreateEmbedFooter, CreateMessage, Mentionable, User,
|
||||
};
|
||||
|
||||
/// Commands related to vouching for new users
|
||||
#[poise::command(slash_command, subcommands("submit", "approve", "deny"))]
|
||||
pub async fn vouch(_ctx: Context<'_>) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Vouch for a new user
|
||||
#[poise::command(slash_command, guild_only)]
|
||||
pub async fn submit(
|
||||
ctx: Context<'_>,
|
||||
#[description = "The user to vouch for"] user: User,
|
||||
) -> Result<(), Error> {
|
||||
// Defer emphemeral response
|
||||
ctx.defer_ephemeral().await?;
|
||||
|
||||
// Do we have a vouch for this user already?
|
||||
if ctx
|
||||
.data()
|
||||
.vouch_store
|
||||
.lock()
|
||||
.await
|
||||
.iter()
|
||||
.any(|vouch| vouch.user.id == user.id)
|
||||
{
|
||||
ctx.say(":x: This user already has a vouch pending!")
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let guild_id = ctx.guild_id().ok_or("Must be run in a guild")?;
|
||||
let member = guild_id.member(ctx.serenity_context(), user.id).await?;
|
||||
|
||||
// Does the user have a silly role?
|
||||
if member
|
||||
.roles
|
||||
.iter()
|
||||
.any(|role_id| *role_id == ctx.data().config.roles.silly_role)
|
||||
{
|
||||
ctx.say(":x: This user already is a vouched member!")
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Create a new vouch
|
||||
let vouch = Vouch::new(user.clone(), ctx.author().clone());
|
||||
|
||||
// Add the vouch to the store, we need to create a mutable reference to .data().vouch_store,
|
||||
// because we want to modify the store - and poise context is immutable by default
|
||||
ctx.data().vouch_store.lock().await.push(vouch);
|
||||
|
||||
// Send a messasge to the mod-logs channel with a ping that a new vouch has been submitted
|
||||
let log_msg = format!(
|
||||
"{} {}\n:notepad_spiral: A new vouch has been submitted for {} by {}, please either approve or deny this vouch.",
|
||||
serenity::model::id::RoleId::new(ctx.data().config.roles.mod_role).mention(),
|
||||
serenity::model::id::RoleId::new(ctx.data().config.roles.admin).mention(),
|
||||
user.clone().mention(),
|
||||
ctx.author().mention()
|
||||
);
|
||||
|
||||
let log_channel_id = ctx.data().config.channels.logs_mod;
|
||||
let channel_hash = guild_id.channels(ctx.serenity_context()).await?;
|
||||
let channel = channel_hash.get(&log_channel_id.into()).unwrap();
|
||||
|
||||
channel
|
||||
.send_message(
|
||||
ctx.serenity_context(),
|
||||
CreateMessage::new().content(log_msg).allowed_mentions(
|
||||
CreateAllowedMentions::new().roles(vec![
|
||||
ctx.data().config.roles.mod_role,
|
||||
ctx.data().config.roles.admin,
|
||||
]),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
||||
ctx.say(":white_check_mark: Vouch submitted! An admin will review the vouch when able.")
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Approve a vouch for a user
|
||||
#[poise::command(slash_command, guild_only)]
|
||||
pub async fn approve(
|
||||
ctx: Context<'_>,
|
||||
#[description = "The user to approve the vouch for"] user: User,
|
||||
) -> Result<(), Error> {
|
||||
// Defer emphemeral response
|
||||
ctx.defer_ephemeral().await?;
|
||||
|
||||
// Grab user
|
||||
let author_user = ctx.author_member().await.clone();
|
||||
match author_user {
|
||||
Some(author_user) => {
|
||||
// Check if the author is an admin
|
||||
if !author_user.roles.iter().any(|role_id| {
|
||||
*role_id == ctx.data().config.roles.admin
|
||||
|| *role_id == ctx.data().config.roles.mod_role
|
||||
}) {
|
||||
ctx.say(":x: You must be an admin to approve vouches!")
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
ctx.say(":x: You must be an admin to approve vouches!")
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have a vouch for this user?
|
||||
let mut vouch_store = ctx.data().vouch_store.lock().await;
|
||||
|
||||
// Find the vouch for the user and its index
|
||||
if let Some((vouch_index, vouch)) = vouch_store
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, v)| v.user.id == user.id)
|
||||
.map(|(index, vouch)| (index, vouch.clone()))
|
||||
{
|
||||
// Add the silly role to the user
|
||||
let guild_id = ctx.guild_id().ok_or("Must be run in a guild")?;
|
||||
let member = guild_id.member(ctx.serenity_context(), user.id).await?;
|
||||
|
||||
member
|
||||
.add_role(ctx.serenity_context(), ctx.data().config.roles.silly_role)
|
||||
.await?;
|
||||
|
||||
// Remove the vouch from the store
|
||||
vouch_store.remove(vouch_index);
|
||||
|
||||
// Send a message to the logs channel (mod)
|
||||
let log_msg = format!(
|
||||
":white_check_mark: Vouch approved for {} by {} at {} CDT, vouched by {}",
|
||||
user.mention(),
|
||||
ctx.author().mention(),
|
||||
vouch.get_vouch_time(),
|
||||
vouch.vouched_by.mention()
|
||||
);
|
||||
|
||||
let public_msg = CreateEmbed::default()
|
||||
.title(format!("Welcome to sillycord, {}!", user.tag()))
|
||||
.description("Enjoy your stay in our silly little community!")
|
||||
.footer(CreateEmbedFooter::new(format!(
|
||||
"Vouched by {} - Approved by {}",
|
||||
vouch.vouched_by.tag(),
|
||||
ctx.author().tag()
|
||||
)))
|
||||
.color(Colour::DARK_PURPLE);
|
||||
|
||||
let log_channel_id = ctx.data().config.channels.logs_mod;
|
||||
let public_channel_id = ctx.data().config.channels.main;
|
||||
|
||||
let channels = guild_id.channels(ctx.serenity_context()).await?;
|
||||
let channel = channels.get(&log_channel_id.into()).unwrap();
|
||||
let public_channel = channels.get(&public_channel_id.into()).unwrap();
|
||||
|
||||
channel
|
||||
.send_message(
|
||||
ctx.serenity_context(),
|
||||
CreateMessage::new().content(log_msg),
|
||||
)
|
||||
.await?;
|
||||
|
||||
public_channel
|
||||
.send_message(
|
||||
ctx.serenity_context(),
|
||||
CreateMessage::new()
|
||||
.embed(public_msg)
|
||||
.content(user.mention().to_string())
|
||||
.allowed_mentions(CreateAllowedMentions::new().users(vec![user.id])),
|
||||
)
|
||||
.await?;
|
||||
|
||||
ctx.data()
|
||||
.database_controller
|
||||
.create_user(user.id.into())
|
||||
.await?;
|
||||
ctx.say(":white_check_mark: Vouch approved!").await?;
|
||||
} else {
|
||||
ctx.say(":x: No vouch found for this user!").await?;
|
||||
}
|
||||
|
||||
drop(vouch_store);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deny a vouch for a user
|
||||
#[poise::command(slash_command, guild_only)]
|
||||
pub async fn deny(
|
||||
ctx: Context<'_>,
|
||||
#[description = "The user to deny the vouch for"] user: User,
|
||||
#[flag] kick: bool,
|
||||
#[description = "The reason for denying the vouch"] _reason: Option<String>,
|
||||
) -> Result<(), Error> {
|
||||
// Defer emphemeral response
|
||||
ctx.defer_ephemeral().await?;
|
||||
|
||||
// Grab user
|
||||
let author_user = ctx.author_member().await.clone();
|
||||
match author_user {
|
||||
Some(author_user) => {
|
||||
// Check if the author is an admin
|
||||
if !author_user.roles.iter().any(|role_id| {
|
||||
*role_id == ctx.data().config.roles.admin
|
||||
|| *role_id == ctx.data().config.roles.mod_role
|
||||
}) {
|
||||
ctx.say(":x: You must be an admin to deny vouches!").await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
ctx.say(":x: You must be an admin to deny vouches!").await?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Do we have a vouch for this user?
|
||||
// Do we have a vouch for this user?
|
||||
let mut vouch_store = ctx.data().vouch_store.lock().await;
|
||||
|
||||
// Find the vouch for the user and its index
|
||||
if let Some((vouch_index, vouch)) = vouch_store
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, v)| v.user.id == user.id)
|
||||
.map(|(index, vouch)| (index, vouch.clone()))
|
||||
{
|
||||
// Remove the vouch from the store
|
||||
vouch_store.remove(vouch_index);
|
||||
|
||||
// Send a message to the logs channel (mod)
|
||||
let log_msg = format!(
|
||||
":x: Vouch denied for {} by {} at {} CDT, vouched by {} with a reason of '{}'",
|
||||
user.mention(),
|
||||
ctx.author().mention(),
|
||||
vouch.get_vouch_time(),
|
||||
vouch.vouched_by.mention(),
|
||||
_reason.clone().unwrap_or("No reason provided".to_string())
|
||||
);
|
||||
|
||||
let log_channel_id = ctx.data().config.channels.logs_mod;
|
||||
let guild = ctx.guild_id().unwrap().clone();
|
||||
let channels = guild.channels(ctx.serenity_context()).await?;
|
||||
let channel = channels.get(&log_channel_id.into()).unwrap();
|
||||
|
||||
channel
|
||||
.send_message(
|
||||
ctx.serenity_context(),
|
||||
CreateMessage::new().content(log_msg),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Then kick the user
|
||||
let member = guild.member(ctx.serenity_context(), user.id).await?;
|
||||
|
||||
// Attempt to notify then kick the user
|
||||
let dm_msg = format!(
|
||||
":warning: Your vouch has been denied by {} with a reason of '{}'. If you have any questions, feel free to ask a moderator or admin.",
|
||||
ctx.author().mention(),
|
||||
_reason.unwrap_or("No reason provided".to_string())
|
||||
);
|
||||
|
||||
let notify_result = vouch
|
||||
.user
|
||||
.direct_message(ctx.serenity_context(), CreateMessage::new().content(dm_msg))
|
||||
.await;
|
||||
|
||||
match notify_result {
|
||||
Ok(_) => {
|
||||
if kick {
|
||||
member.kick(ctx.serenity_context()).await?;
|
||||
ctx.say(":white_check_mark: Vouch denied and user kicked! User notified.")
|
||||
.await?;
|
||||
} else {
|
||||
ctx.say(":white_check_mark: Vouch denied! User notified.")
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
if kick {
|
||||
member.kick(ctx.serenity_context()).await?;
|
||||
ctx.say(":white_check_mark: Vouch denied and user kicked! User not notified.")
|
||||
.await?;
|
||||
} else {
|
||||
ctx.say(":white_check_mark: Vouch denied! User not notified.")
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ctx.say(":x: No vouch found for this user!").await?;
|
||||
}
|
||||
|
||||
drop(vouch_store);
|
||||
Ok(())
|
||||
}
|
@ -1,13 +1,16 @@
|
||||
use crate::{Data, Error};
|
||||
use poise::serenity_prelude::{self as serenity, ActivityData, Interaction, OnlineStatus};
|
||||
use tracing::{info, warn};
|
||||
use crate::{handlers, utils::get_rustc_version, Data, Error};
|
||||
use ::serenity::all::{
|
||||
ChannelId, Colour, CreateEmbed, CreateEmbedFooter, CreateMessage, Mentionable,
|
||||
};
|
||||
use poise::serenity_prelude::{self as serenity, ActivityData, OnlineStatus};
|
||||
use tracing::{error, info};
|
||||
|
||||
// Create a span for every event
|
||||
#[tracing::instrument(skip(ctx, event, framework, data))]
|
||||
#[tracing::instrument(skip(ctx, event, _framework, data))]
|
||||
pub async fn event_handler(
|
||||
ctx: &serenity::Context,
|
||||
event: &serenity::FullEvent,
|
||||
framework: poise::FrameworkContext<'_, Data, Error>,
|
||||
_framework: poise::FrameworkContext<'_, Data, Error>,
|
||||
data: &Data,
|
||||
) -> Result<(), Error> {
|
||||
match event {
|
||||
@ -17,6 +20,82 @@ pub async fn event_handler(
|
||||
Some(ActivityData::watching("sillycord")),
|
||||
OnlineStatus::Online,
|
||||
);
|
||||
|
||||
let embed = CreateEmbed::default()
|
||||
.title("Bot Ready!")
|
||||
.description(format!(
|
||||
"Bot is ready! Running on rust version {}, sillycord-bot version {}",
|
||||
get_rustc_version().await,
|
||||
env!("CARGO_PKG_VERSION")
|
||||
))
|
||||
.footer(CreateEmbedFooter::new(
|
||||
"Bot created for sillycord. Made with ❤️ by sticks and others",
|
||||
))
|
||||
.color(Colour::DARK_GREEN);
|
||||
|
||||
let msg = CreateMessage::default().embed(embed);
|
||||
let channel_id = ChannelId::new(data.config.channels.logs_public);
|
||||
|
||||
// We have to use http here to be future safe and not block the event loop
|
||||
ctx.http.send_message(channel_id, vec![], &msg).await?;
|
||||
}
|
||||
|
||||
serenity::FullEvent::GuildMemberAddition { new_member, .. } => {
|
||||
// Is the new user in the main guild?
|
||||
if new_member.guild_id != data.config.main_guild_id {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
info!("Handling new user {}", new_member.user.tag());
|
||||
|
||||
ctx.http
|
||||
.send_message(
|
||||
ChannelId::new(data.config.channels.logs_mod),
|
||||
vec![],
|
||||
&CreateMessage::default().content(format!(
|
||||
"<:join:1310407968503894158> New user joined {} - created at: <t:{}:f>",
|
||||
new_member.mention(),
|
||||
new_member.user.created_at().timestamp_millis() / 1000
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let handler_result =
|
||||
handlers::join::join_handler(ctx.clone(), data, new_member.clone()).await;
|
||||
|
||||
if let Err(e) = handler_result {
|
||||
error!(
|
||||
"Error invoking join handler for new user {}: {:?}",
|
||||
new_member.user.tag(),
|
||||
e
|
||||
);
|
||||
} else {
|
||||
info!(
|
||||
"Join handler completed successfully for new user {}",
|
||||
new_member.user.tag()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
serenity::FullEvent::GuildMemberRemoval { user, .. } => {
|
||||
// We can safely ignore the user's guild_id here, as they are leaving the guild -
|
||||
// because the bot is only in one guild, we know the user is leaving the main guild
|
||||
info!("Handling user leave {}", user.tag());
|
||||
|
||||
data.database_controller
|
||||
.delete_user_by_discord_id(user.id.into())
|
||||
.await?;
|
||||
|
||||
ctx.http
|
||||
.send_message(
|
||||
ChannelId::new(data.config.channels.logs_mod),
|
||||
vec![],
|
||||
&CreateMessage::default().content(format!(
|
||||
"<:leave:1310407968503894158> User left {}",
|
||||
user.mention()
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
serenity::FullEvent::ShardsReady { total_shards, .. } => {
|
||||
|
102
src/handlers/db.rs
Normal file
102
src/handlers/db.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use crate::structs::user::User;
|
||||
use sqlx::MySqlPool;
|
||||
|
||||
pub struct DatabaseController {
|
||||
db: MySqlPool,
|
||||
}
|
||||
|
||||
impl DatabaseController {
|
||||
pub fn new(db: MySqlPool) -> Self {
|
||||
Self { db }
|
||||
}
|
||||
|
||||
pub async fn get_user_by_discord_id(
|
||||
&self,
|
||||
discord_id: u64,
|
||||
) -> Result<Option<User>, sqlx::Error> {
|
||||
let user = sqlx::query!("SELECT * FROM users WHERE discord_id = ?", discord_id)
|
||||
.fetch_optional(&self.db)
|
||||
.await?;
|
||||
|
||||
match user {
|
||||
Some(user) => Ok(Some(User {
|
||||
id: user.id,
|
||||
discord_id: user
|
||||
.discord_id
|
||||
.parse::<u64>()
|
||||
.map_err(|_| sqlx::Error::Decode("Failed to parse discord_id".into()))?,
|
||||
actions_allowed: user.actions_allowed == Some(1),
|
||||
about: user.about,
|
||||
pronouns: user.pronouns,
|
||||
})),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_user_by_id(&self, id: u64) -> Result<Option<User>, sqlx::Error> {
|
||||
let user = sqlx::query!("SELECT * FROM users WHERE id = ?", id)
|
||||
.fetch_optional(&self.db)
|
||||
.await?;
|
||||
|
||||
match user {
|
||||
Some(user) => Ok(Some(User {
|
||||
id: user.id,
|
||||
discord_id: user
|
||||
.discord_id
|
||||
.parse::<u64>()
|
||||
.map_err(|_| sqlx::Error::Decode("Failed to parse discord_id".into()))?,
|
||||
actions_allowed: user.actions_allowed == Some(1),
|
||||
about: user.about,
|
||||
pronouns: user.pronouns,
|
||||
})),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_user(&self, discord_id: u64) -> Result<User, sqlx::Error> {
|
||||
let user = sqlx::query!(
|
||||
"INSERT INTO users (discord_id) VALUES (?)",
|
||||
discord_id.to_string()
|
||||
)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
|
||||
Ok(User {
|
||||
id: user.last_insert_id(),
|
||||
discord_id,
|
||||
actions_allowed: true,
|
||||
about: None,
|
||||
pronouns: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn update_user(&self, user: User) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"UPDATE users SET actions_allowed = ?, about = ?, pronouns = ? WHERE id = ?",
|
||||
user.actions_allowed as i8,
|
||||
user.about,
|
||||
user.pronouns,
|
||||
user.id
|
||||
)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_user(&self, user: User) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM users WHERE id = ?", user.id)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_user_by_discord_id(&self, discord_id: u64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!("DELETE FROM users WHERE discord_id = ?", discord_id)
|
||||
.execute(&self.db)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
73
src/handlers/join.rs
Normal file
73
src/handlers/join.rs
Normal file
@ -0,0 +1,73 @@
|
||||
use serenity::all::{
|
||||
ChannelId, Colour, Context, CreateAllowedMentions, CreateEmbed, CreateEmbedFooter,
|
||||
CreateMessage, Member, Mentionable,
|
||||
};
|
||||
use std::error::Error;
|
||||
use tracing::{error, warn};
|
||||
|
||||
#[tracing::instrument(skip(ctx, new_member, data))]
|
||||
pub async fn join_handler(
|
||||
ctx: Context,
|
||||
data: &crate::Data,
|
||||
new_member: Member,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let welcome_msg = CreateEmbed::default()
|
||||
.title("Welcome to sillycord!")
|
||||
.description(format!(
|
||||
"Welcome! To keep sillycord a safe and fun place, we require all newly invited members to be vouched by a current member. Please wait for a member of the community to vouch for you. If you do not receive a vouch within 24 hours, you will be removed from the server. If you have any questions, feel free to ask a moderator or admin.",
|
||||
))
|
||||
.color(Colour::PURPLE)
|
||||
.footer(CreateEmbedFooter::new(
|
||||
"I am a bot, and this action was performed automatically. If you have any questions or concerns, please contact a moderator or admin.",
|
||||
));
|
||||
|
||||
// Try to send the welcome message to the new member
|
||||
let dm_result = new_member
|
||||
.user
|
||||
.direct_message(&ctx, CreateMessage::default().embed(welcome_msg.clone()))
|
||||
.await;
|
||||
|
||||
if let Err(why) = dm_result {
|
||||
warn!(
|
||||
"Failed to send welcome message to {}: {:?} - defaulting to public channel",
|
||||
new_member.user.tag(),
|
||||
why
|
||||
);
|
||||
|
||||
let new_welcome = welcome_msg.clone().footer(CreateEmbedFooter::new(
|
||||
"We were unable to send you a direct message. We've posted the welcome message here instead - please make sure to read it!",
|
||||
));
|
||||
|
||||
let send_result = ctx
|
||||
.http
|
||||
.send_message(
|
||||
ChannelId::new(data.config.channels.welcome),
|
||||
vec![],
|
||||
&CreateMessage::default()
|
||||
.embed(new_welcome)
|
||||
.content(format!(
|
||||
"{} Please make sure to read the welcome message below!",
|
||||
new_member.mention()
|
||||
))
|
||||
.allowed_mentions(CreateAllowedMentions::new().users(vec![new_member.user.id])),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(why) = send_result {
|
||||
error!(
|
||||
"Failed to send welcome message to {} in public channel: {:?}",
|
||||
new_member.user.tag(),
|
||||
why
|
||||
);
|
||||
|
||||
error!(
|
||||
"All options to send welcome message to {} failed - aborting",
|
||||
new_member.user.tag()
|
||||
);
|
||||
|
||||
return Err(why.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
pub mod join;
|
||||
pub mod db;
|
92
src/main.rs
92
src/main.rs
@ -1,10 +1,19 @@
|
||||
mod commands;
|
||||
mod events;
|
||||
mod handlers;
|
||||
mod structs;
|
||||
mod utils;
|
||||
|
||||
use events::event_handler;
|
||||
use serenity::all::{ClientBuilder, GatewayIntents};
|
||||
use handlers::db::DatabaseController;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::{
|
||||
all::{ClientBuilder, GatewayIntents},
|
||||
futures::lock::Mutex,
|
||||
};
|
||||
use sqlx::mysql::MySqlPoolOptions;
|
||||
use sqlx::MySqlPool;
|
||||
use structs::vouch::Vouch;
|
||||
use tracing::{event, info, info_span, Level};
|
||||
|
||||
fn check_required_env_vars() {
|
||||
@ -68,11 +77,37 @@ async fn init_sqlx() -> MySqlPool {
|
||||
}
|
||||
|
||||
struct Data {
|
||||
sqlx_pool: MySqlPool,
|
||||
database_controller: DatabaseController,
|
||||
uptime: std::time::Instant,
|
||||
config: Config,
|
||||
vouch_store: Mutex<Vec<Vouch>>,
|
||||
} // User data, which is stored and accessible in all command invocations
|
||||
type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||
|
||||
pub type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||
type Context<'a> = poise::Context<'a, Data, Error>;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Config {
|
||||
main_guild_id: u64,
|
||||
channels: Channels,
|
||||
roles: Roles,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Channels {
|
||||
welcome: u64,
|
||||
main: u64,
|
||||
logs_public: u64,
|
||||
logs_mod: u64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
struct Roles {
|
||||
admin: u64,
|
||||
mod_role: u64,
|
||||
silly_role: u64,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let start_time = std::time::Instant::now();
|
||||
@ -88,15 +123,57 @@ async fn main() {
|
||||
info!("checking required environment variables");
|
||||
check_required_env_vars();
|
||||
|
||||
info!("loading config");
|
||||
// Do we have a config.toml file? If we do, load it
|
||||
// If we don't, create it with the default values, then exit the program and tell the user to fill it out
|
||||
let config: Config = match std::fs::read_to_string("config.toml") {
|
||||
Ok(config) => toml::from_str(&config).expect("failed to parse config.toml"),
|
||||
Err(_) => {
|
||||
let default_config = Config {
|
||||
main_guild_id: 0,
|
||||
channels: Channels {
|
||||
welcome: 0,
|
||||
main: 0,
|
||||
logs_public: 0,
|
||||
logs_mod: 0,
|
||||
},
|
||||
roles: Roles {
|
||||
admin: 0,
|
||||
mod_role: 0,
|
||||
silly_role: 0,
|
||||
},
|
||||
};
|
||||
let default_config_toml = toml::to_string_pretty(&default_config).unwrap();
|
||||
std::fs::write("config.toml", default_config_toml)
|
||||
.expect("failed to write config.toml");
|
||||
event!(Level::WARN, "config.toml not found, created a default one");
|
||||
event!(
|
||||
Level::ERROR,
|
||||
"please fill out config.toml and restart the bot"
|
||||
);
|
||||
|
||||
panic!("config.toml not found, created a default one, please fill it out and restart the bot");
|
||||
}
|
||||
};
|
||||
|
||||
info!("initializing SQLx");
|
||||
let pool = init_sqlx().await;
|
||||
|
||||
info!("initializing bot");
|
||||
let intents = GatewayIntents::non_privileged();
|
||||
let intents = GatewayIntents::privileged();
|
||||
|
||||
let framework = poise::Framework::builder()
|
||||
.options(poise::FrameworkOptions::<Data, Error> {
|
||||
commands: vec![commands::ping::ping()],
|
||||
commands: vec![
|
||||
commands::ping::ping(),
|
||||
commands::vouch::vouch(),
|
||||
commands::profile::profiles(),
|
||||
commands::dog::dog(),
|
||||
commands::cta::cta(),
|
||||
commands::action::use_action_hug(),
|
||||
commands::action::use_action_kiss(),
|
||||
commands::action::use_action_pat(),
|
||||
],
|
||||
event_handler: |ctx, event, framework, data| {
|
||||
Box::pin(event_handler(ctx, event, framework, data))
|
||||
},
|
||||
@ -107,7 +184,10 @@ async fn main() {
|
||||
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
||||
Ok(Data {
|
||||
// Initialize user data here
|
||||
sqlx_pool: pool,
|
||||
database_controller: DatabaseController::new(pool.clone()),
|
||||
uptime: std::time::Instant::now(),
|
||||
config,
|
||||
vouch_store: Mutex::new(Vec::new()),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
2
src/structs/mod.rs
Normal file
2
src/structs/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod vouch;
|
||||
pub mod user;
|
7
src/structs/user.rs
Normal file
7
src/structs/user.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub struct User {
|
||||
pub id: u64,
|
||||
pub discord_id: u64,
|
||||
pub actions_allowed: bool,
|
||||
pub about: Option<String>,
|
||||
pub pronouns: Option<String>,
|
||||
}
|
27
src/structs/vouch.rs
Normal file
27
src/structs/vouch.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use chrono_tz::US::Central;
|
||||
use serenity::all::User;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Vouch {
|
||||
pub user: User,
|
||||
pub vouched_by: User,
|
||||
pub vouch_time: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl Vouch {
|
||||
pub fn new(user: User, vouched_by: User) -> Self {
|
||||
Self {
|
||||
user,
|
||||
vouched_by,
|
||||
vouch_time: Utc::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_vouch_time(&self) -> String {
|
||||
self.vouch_time
|
||||
.with_timezone(&Central)
|
||||
.format("%Y-%m-%d %H:%M:%S")
|
||||
.to_string()
|
||||
}
|
||||
}
|
30
src/utils.rs
Normal file
30
src/utils.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use std::process::Command;
|
||||
|
||||
pub async fn get_rustc_version() -> String {
|
||||
let rustc_version = Command::new("rustc")
|
||||
.arg("--version")
|
||||
.output()
|
||||
.expect("failed to get rustc version");
|
||||
|
||||
String::from_utf8(rustc_version.stdout).expect("failed to convert rustc version to string")
|
||||
}
|
||||
|
||||
pub async fn get_random_action_image(action: String) -> Result<String, reqwest::Error> {
|
||||
let action = action.to_lowercase();
|
||||
let action = action.replace(" ", "_");
|
||||
|
||||
let url = format!("https://api.otakugifs.xyz/gif?reaction={}", action);
|
||||
let response = reqwest::get(&url)
|
||||
.await
|
||||
.expect("failed to get random action image");
|
||||
|
||||
let response_json: serde_json::Value =
|
||||
serde_json::from_str(&response.text().await.expect("failed to get response text"))
|
||||
.expect("failed to parse response json");
|
||||
|
||||
let image_url = response_json["url"]
|
||||
.as_str()
|
||||
.expect("failed to get image url from response json");
|
||||
|
||||
Ok(image_url.to_string())
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user