Compare commits
29 commits
main
...
#17-automa
Author | SHA1 | Date | |
---|---|---|---|
77a7b7b81d | |||
2f75dc41c8 | |||
c98baa9d72 | |||
e4a8cce725 | |||
5b22f699d6 | |||
6739c7e068 | |||
d673dce6fa | |||
015f23b922 | |||
7a6421469c | |||
733827c3e6 | |||
344d6d3585 | |||
b7161e2614 | |||
32249364ff | |||
f1138a3c81 | |||
61e76db8dd | |||
3e6dc9d560 | |||
aff6299ac7 | |||
bd80bda22f | |||
fe5aa5b252 | |||
273c58d035 | |||
3927734083 | |||
41407ecefb | |||
79f880daea | |||
ceade9b972 | |||
b2d8238c17 | |||
a7e8f5698e | |||
0e1a7d56b6 | |||
fd32adb138 | |||
04a487cd8f |
19 changed files with 1720 additions and 1108 deletions
341
Cargo.lock
generated
341
Cargo.lock
generated
|
@ -1024,7 +1024,26 @@ dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http 0.2.12",
|
||||||
|
"indexmap",
|
||||||
|
"slab",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "h2"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-waker",
|
||||||
|
"bytes 1.7.1",
|
||||||
|
"fnv",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"http 1.1.0",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -1147,6 +1166,17 @@ dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
||||||
|
dependencies = [
|
||||||
|
"bytes 1.7.1",
|
||||||
|
"fnv",
|
||||||
|
"itoa",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http-body"
|
name = "http-body"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
|
@ -1154,7 +1184,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes 1.7.1",
|
"bytes 1.7.1",
|
||||||
"http",
|
"http 0.2.12",
|
||||||
|
"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 1.7.1",
|
||||||
|
"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 1.7.1",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.1.0",
|
||||||
|
"http-body 1.0.1",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1216,9 +1269,9 @@ dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"h2",
|
"h2 0.3.26",
|
||||||
"http",
|
"http 0.2.12",
|
||||||
"http-body",
|
"http-body 0.4.6",
|
||||||
"httparse",
|
"httparse",
|
||||||
"httpdate",
|
"httpdate",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
@ -1230,6 +1283,26 @@ dependencies = [
|
||||||
"want",
|
"want",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a"
|
||||||
|
dependencies = [
|
||||||
|
"bytes 1.7.1",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-util",
|
||||||
|
"h2 0.4.6",
|
||||||
|
"http 1.1.0",
|
||||||
|
"http-body 1.0.1",
|
||||||
|
"httparse",
|
||||||
|
"itoa",
|
||||||
|
"pin-project-lite",
|
||||||
|
"smallvec",
|
||||||
|
"tokio",
|
||||||
|
"want",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-rustls"
|
name = "hyper-rustls"
|
||||||
version = "0.24.2"
|
version = "0.24.2"
|
||||||
|
@ -1237,13 +1310,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http 0.2.12",
|
||||||
"hyper",
|
"hyper 0.14.30",
|
||||||
"rustls 0.21.12",
|
"rustls 0.21.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.1",
|
"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.0",
|
||||||
|
"hyper-util",
|
||||||
|
"rustls 0.23.16",
|
||||||
|
"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 1.7.1",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper 1.5.0",
|
||||||
|
"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 1.7.1",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.1.0",
|
||||||
|
"http-body 1.0.1",
|
||||||
|
"hyper 1.5.0",
|
||||||
|
"pin-project-lite",
|
||||||
|
"socket2 0.5.7",
|
||||||
|
"tokio",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "iana-time-zone"
|
name = "iana-time-zone"
|
||||||
version = "0.1.61"
|
version = "0.1.61"
|
||||||
|
@ -1330,7 +1455,7 @@ dependencies = [
|
||||||
"curl-sys",
|
"curl-sys",
|
||||||
"flume 0.9.2",
|
"flume 0.9.2",
|
||||||
"futures-lite 1.13.0",
|
"futures-lite 1.13.0",
|
||||||
"http",
|
"http 0.2.12",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"slab",
|
"slab",
|
||||||
|
@ -2014,11 +2139,11 @@ dependencies = [
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"h2",
|
"h2 0.3.26",
|
||||||
"http",
|
"http 0.2.12",
|
||||||
"http-body",
|
"http-body 0.4.6",
|
||||||
"hyper",
|
"hyper 0.14.30",
|
||||||
"hyper-rustls",
|
"hyper-rustls 0.24.2",
|
||||||
"ipnet",
|
"ipnet",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"log",
|
"log",
|
||||||
|
@ -2028,12 +2153,12 @@ dependencies = [
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustls 0.21.12",
|
"rustls 0.21.12",
|
||||||
"rustls-pemfile",
|
"rustls-pemfile 1.0.4",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper",
|
"sync_wrapper 0.1.2",
|
||||||
"system-configuration",
|
"system-configuration 0.5.1",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls 0.24.1",
|
"tokio-rustls 0.24.1",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
@ -2047,6 +2172,49 @@ dependencies = [
|
||||||
"winreg",
|
"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 1.7.1",
|
||||||
|
"encoding_rs",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"h2 0.4.6",
|
||||||
|
"http 1.1.0",
|
||||||
|
"http-body 1.0.1",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper 1.5.0",
|
||||||
|
"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.1",
|
||||||
|
"system-configuration 0.6.1",
|
||||||
|
"tokio",
|
||||||
|
"tokio-native-tls",
|
||||||
|
"tower-service",
|
||||||
|
"url",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"windows-registry",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.16.20"
|
version = "0.16.20"
|
||||||
|
@ -2145,10 +2313,23 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"ring 0.17.8",
|
"ring 0.17.8",
|
||||||
"rustls-webpki",
|
"rustls-webpki 0.101.7",
|
||||||
"sct",
|
"sct",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls"
|
||||||
|
version = "0.23.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"rustls-webpki 0.102.8",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-pemfile"
|
name = "rustls-pemfile"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -2158,6 +2339,21 @@ dependencies = [
|
||||||
"base64 0.21.7",
|
"base64 0.21.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pemfile"
|
||||||
|
version = "2.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
|
||||||
|
dependencies = [
|
||||||
|
"rustls-pki-types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-pki-types"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.101.7"
|
version = "0.101.7"
|
||||||
|
@ -2168,6 +2364,17 @@ dependencies = [
|
||||||
"untrusted 0.9.0",
|
"untrusted 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustls-webpki"
|
||||||
|
version = "0.102.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
|
||||||
|
dependencies = [
|
||||||
|
"ring 0.17.8",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"untrusted 0.9.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ryu"
|
name = "ryu"
|
||||||
version = "1.0.18"
|
version = "1.0.18"
|
||||||
|
@ -2321,7 +2528,7 @@ dependencies = [
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"reqwest",
|
"reqwest 0.11.27",
|
||||||
"serde",
|
"serde",
|
||||||
"serde-value",
|
"serde-value",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -2399,6 +2606,15 @@ version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signature"
|
name = "signature"
|
||||||
version = "2.2.0"
|
version = "2.2.0"
|
||||||
|
@ -2418,7 +2634,9 @@ dependencies = [
|
||||||
"lettre",
|
"lettre",
|
||||||
"maud",
|
"maud",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
"reqwest 0.12.9",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"serenity",
|
"serenity",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"surf",
|
"surf",
|
||||||
|
@ -2834,6 +3052,15 @@ version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sync_wrapper"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "system-configuration"
|
name = "system-configuration"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -2842,7 +3069,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"core-foundation",
|
"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]]
|
[[package]]
|
||||||
|
@ -2855,6 +3093,16 @@ dependencies = [
|
||||||
"libc",
|
"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]]
|
[[package]]
|
||||||
name = "tempfile"
|
name = "tempfile"
|
||||||
version = "3.12.0"
|
version = "3.12.0"
|
||||||
|
@ -2982,7 +3230,9 @@ dependencies = [
|
||||||
"bytes 1.7.1",
|
"bytes 1.7.1",
|
||||||
"libc",
|
"libc",
|
||||||
"mio",
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
"socket2 0.5.7",
|
"socket2 0.5.7",
|
||||||
"tokio-macros",
|
"tokio-macros",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
|
@ -2999,6 +3249,16 @@ dependencies = [
|
||||||
"syn 2.0.77",
|
"syn 2.0.77",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "tokio-rustls"
|
name = "tokio-rustls"
|
||||||
version = "0.23.4"
|
version = "0.23.4"
|
||||||
|
@ -3020,6 +3280,17 @@ dependencies = [
|
||||||
"tokio",
|
"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.16",
|
||||||
|
"rustls-pki-types",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
|
@ -3107,7 +3378,7 @@ dependencies = [
|
||||||
"base64 0.13.1",
|
"base64 0.13.1",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes 1.7.1",
|
"bytes 1.7.1",
|
||||||
"http",
|
"http 0.2.12",
|
||||||
"httparse",
|
"httparse",
|
||||||
"log",
|
"log",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
@ -3432,6 +3703,36 @@ dependencies = [
|
||||||
"windows-targets 0.52.6",
|
"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]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
|
|
|
@ -17,7 +17,8 @@ name = "update_minecraft"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# discord library
|
# discord library
|
||||||
serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] }
|
serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] }
|
||||||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "full"] }
|
||||||
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
|
|
||||||
# to make the http requests
|
# to make the http requests
|
||||||
surf = "2.3.2"
|
surf = "2.3.2"
|
||||||
|
@ -26,6 +27,7 @@ dotenvy = "0.15.7"
|
||||||
|
|
||||||
# For sqlite
|
# For sqlite
|
||||||
sqlx = { version = "0.7.1", features = [ "runtime-tokio", "sqlite", "migrate" ] }
|
sqlx = { version = "0.7.1", features = [ "runtime-tokio", "sqlite", "migrate" ] }
|
||||||
|
serde_json = { version = "1.0", features = ["raw_value"] }
|
||||||
|
|
||||||
# create random strings
|
# create random strings
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
10
db/migrations/7_committee-mk-ii.sql
Normal file
10
db/migrations/7_committee-mk-ii.sql
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
-- No longer using the previous committee table
|
||||||
|
DROP TABLE committee;
|
||||||
|
|
||||||
|
-- new table pulling from teh api
|
||||||
|
CREATE TABLE IF NOT EXISTS committees (
|
||||||
|
id integer PRIMARY KEY,
|
||||||
|
name text not null,
|
||||||
|
link text not null,
|
||||||
|
committee text not null
|
||||||
|
);
|
|
@ -4,7 +4,10 @@ use serenity::{
|
||||||
model::gateway::{GatewayIntents, Ready},
|
model::gateway::{GatewayIntents, Ready},
|
||||||
Client,
|
Client,
|
||||||
};
|
};
|
||||||
use skynet_discord_bot::{db_init, get_config, get_data::get_wolves, Config, DataBase};
|
use skynet_discord_bot::common::database::{db_init, DataBase};
|
||||||
|
use skynet_discord_bot::common::wolves::cns::get_wolves;
|
||||||
|
use skynet_discord_bot::common::wolves::committees::get_cns;
|
||||||
|
use skynet_discord_bot::{get_config, Config};
|
||||||
use std::{process, sync::Arc};
|
use std::{process, sync::Arc};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
@ -43,8 +46,12 @@ impl EventHandler for Handler {
|
||||||
let ctx = Arc::new(ctx);
|
let ctx = Arc::new(ctx);
|
||||||
println!("{} is connected!", ready.user.name);
|
println!("{} is connected!", ready.user.name);
|
||||||
|
|
||||||
|
// get the data for each individual club/soc
|
||||||
get_wolves(&ctx).await;
|
get_wolves(&ctx).await;
|
||||||
|
|
||||||
|
// get teh data for the clubs/socs committees
|
||||||
|
get_cns(&ctx).await;
|
||||||
|
|
||||||
// finish up
|
// finish up
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use skynet_discord_bot::{db_init, get_config, get_minecraft_config, update_server, whitelist_wipe};
|
use skynet_discord_bot::common::database::db_init;
|
||||||
|
use skynet_discord_bot::common::minecraft::{get_minecraft_config, update_server, whitelist_wipe};
|
||||||
|
use skynet_discord_bot::get_config;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
|
|
@ -4,7 +4,9 @@ use serenity::{
|
||||||
model::gateway::{GatewayIntents, Ready},
|
model::gateway::{GatewayIntents, Ready},
|
||||||
Client,
|
Client,
|
||||||
};
|
};
|
||||||
use skynet_discord_bot::{db_init, get_config, get_server_config_bulk, set_roles, Config, DataBase};
|
use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase};
|
||||||
|
use skynet_discord_bot::common::set_roles::{committee, normal};
|
||||||
|
use skynet_discord_bot::{get_config, Config};
|
||||||
use std::{process, sync::Arc};
|
use std::{process, sync::Arc};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
@ -43,14 +45,18 @@ impl EventHandler for Handler {
|
||||||
let ctx = Arc::new(ctx);
|
let ctx = Arc::new(ctx);
|
||||||
println!("{} is connected!", ready.user.name);
|
println!("{} is connected!", ready.user.name);
|
||||||
|
|
||||||
bulk_check(Arc::clone(&ctx)).await;
|
// this goes into each server and sets roles for each wolves member
|
||||||
|
check_bulk(Arc::clone(&ctx)).await;
|
||||||
|
|
||||||
|
// u[date committee server
|
||||||
|
committee::check_committee(Arc::clone(&ctx)).await;
|
||||||
|
|
||||||
// finish up
|
// finish up
|
||||||
process::exit(0);
|
process::exit(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn bulk_check(ctx: Arc<Context>) {
|
async fn check_bulk(ctx: Arc<Context>) {
|
||||||
let db_lock = {
|
let db_lock = {
|
||||||
let data_read = ctx.data.read().await;
|
let data_read = ctx.data.read().await;
|
||||||
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
|
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
|
||||||
|
@ -59,6 +65,6 @@ async fn bulk_check(ctx: Arc<Context>) {
|
||||||
let db = db_lock.read().await;
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
for server_config in get_server_config_bulk(&db).await {
|
for server_config in get_server_config_bulk(&db).await {
|
||||||
set_roles::update_server(&ctx, &server_config, &[], &[]).await;
|
normal::update_server(&ctx, &server_config, &[], &[]).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,10 @@ use serenity::{
|
||||||
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use skynet_discord_bot::get_data::get_wolves;
|
use skynet_discord_bot::common::database::{get_server_config, DataBase, Servers};
|
||||||
use skynet_discord_bot::{get_server_config, is_admin, set_roles::update_server, DataBase, Servers};
|
use skynet_discord_bot::common::set_roles::normal::update_server;
|
||||||
|
use skynet_discord_bot::common::wolves::cns::get_wolves;
|
||||||
|
use skynet_discord_bot::is_admin;
|
||||||
use sqlx::{Error, Pool, Sqlite};
|
use sqlx::{Error, Pool, Sqlite};
|
||||||
|
|
||||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||||
|
|
|
@ -1,311 +0,0 @@
|
||||||
use lettre::{
|
|
||||||
message::{header, MultiPart, SinglePart},
|
|
||||||
transport::smtp::{self, authentication::Credentials},
|
|
||||||
Message, SmtpTransport, Transport,
|
|
||||||
};
|
|
||||||
use maud::html;
|
|
||||||
use serenity::{
|
|
||||||
builder::CreateApplicationCommand,
|
|
||||||
client::Context,
|
|
||||||
model::{
|
|
||||||
application::interaction::application_command::ApplicationCommandInteraction,
|
|
||||||
id::UserId,
|
|
||||||
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use skynet_discord_bot::{random_string, Config, DataBase};
|
|
||||||
use sqlx::{Pool, Sqlite};
|
|
||||||
|
|
||||||
pub mod link {
|
|
||||||
use super::*;
|
|
||||||
use serenity::model::id::GuildId;
|
|
||||||
use skynet_discord_bot::Committee;
|
|
||||||
|
|
||||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
|
||||||
let committee_server = GuildId(1220150752656363520);
|
|
||||||
match command.guild_id {
|
|
||||||
None => {
|
|
||||||
return "Not in correct discord server.".to_string();
|
|
||||||
}
|
|
||||||
Some(x) => {
|
|
||||||
if x != committee_server {
|
|
||||||
return "Not in correct discord server.".to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let option = command
|
|
||||||
.data
|
|
||||||
.options
|
|
||||||
.first()
|
|
||||||
.expect("Expected email option")
|
|
||||||
.resolved
|
|
||||||
.as_ref()
|
|
||||||
.expect("Expected email object");
|
|
||||||
|
|
||||||
let email = if let CommandDataOptionValue::String(email) = option {
|
|
||||||
email.trim()
|
|
||||||
} else {
|
|
||||||
return "Please provide a valid committee email.".to_string();
|
|
||||||
};
|
|
||||||
|
|
||||||
// fail early
|
|
||||||
if !email.ends_with("@ulwolves.ie") {
|
|
||||||
return "Please use a @ulwolves.ie address you have access to.".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
let db_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let db = db_lock.read().await;
|
|
||||||
|
|
||||||
let config_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config = config_lock.read().await;
|
|
||||||
|
|
||||||
if get_server_member_discord(&db, &command.user.id).await.is_some() {
|
|
||||||
return "Already linked".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
if get_verify_from_db(&db, &command.user.id).await.is_some() {
|
|
||||||
return "Linking already in process, please check email.".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate a auth key
|
|
||||||
let auth = random_string(20);
|
|
||||||
match send_mail(&config, email, &auth, &command.user.name) {
|
|
||||||
Ok(_) => match save_to_db(&db, email, &auth, &command.user.id).await {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
return format!("Unable to save to db {} {e:?}", email);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
return format!("Unable to send mail to {} {e:?}", email);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
|
||||||
command
|
|
||||||
.name("link_committee")
|
|
||||||
.description("Verify you are a committee member")
|
|
||||||
.create_option(|option| {
|
|
||||||
option
|
|
||||||
.name("email")
|
|
||||||
.description("UL Wolves Committee Email")
|
|
||||||
.kind(CommandOptionType::String)
|
|
||||||
.required(true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_member_discord(db: &Pool<Sqlite>, user: &UserId) -> Option<Committee> {
|
|
||||||
sqlx::query_as::<_, Committee>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM committee
|
|
||||||
WHERE discord = ?
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*user.as_u64() as i64)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn send_mail(config: &Config, mail: &str, auth: &str, user: &str) -> Result<smtp::response::Response, smtp::Error> {
|
|
||||||
let sender = format!("UL Computer Society <{}>", &config.mail_user);
|
|
||||||
|
|
||||||
// Create the html we want to send.
|
|
||||||
let html = html! {
|
|
||||||
head {
|
|
||||||
title { "Hello from Skynet!" }
|
|
||||||
style type="text/css" {
|
|
||||||
"h2, h4 { font-family: Arial, Helvetica, sans-serif; }"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
div style="display: flex; flex-direction: column; align-items: center;" {
|
|
||||||
h2 { "Hello from Skynet!" }
|
|
||||||
// Substitute in the name of our recipient.
|
|
||||||
p { "Hi " (user) "," }
|
|
||||||
p {
|
|
||||||
"Please use " pre { "/verify_committee code: " (auth)} " to verify your discord account."
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
"Skynet Team"
|
|
||||||
br;
|
|
||||||
"UL Computer Society"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let body_text = format!(
|
|
||||||
r#"
|
|
||||||
Hi {user}
|
|
||||||
|
|
||||||
Please use "/verify_committee code: {auth}" to verify your discord account.
|
|
||||||
|
|
||||||
Skynet Team
|
|
||||||
UL Computer Society
|
|
||||||
"#
|
|
||||||
);
|
|
||||||
|
|
||||||
// Build the message.
|
|
||||||
let email = Message::builder()
|
|
||||||
.from(sender.parse().unwrap())
|
|
||||||
.to(mail.parse().unwrap())
|
|
||||||
.subject("Skynet-Discord: Link Committee.")
|
|
||||||
.multipart(
|
|
||||||
// This is composed of two parts.
|
|
||||||
// also helps not trip spam settings (uneven number of url's
|
|
||||||
MultiPart::alternative()
|
|
||||||
.singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body_text))
|
|
||||||
.singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML).body(html.into_string())),
|
|
||||||
)
|
|
||||||
.expect("failed to build email");
|
|
||||||
|
|
||||||
let creds = Credentials::new(config.mail_user.clone(), config.mail_pass.clone());
|
|
||||||
|
|
||||||
// Open a remote connection to gmail using STARTTLS
|
|
||||||
let mailer = SmtpTransport::starttls_relay(&config.mail_smtp)?.credentials(creds).build();
|
|
||||||
|
|
||||||
// Send the email
|
|
||||||
mailer.send(&email)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_verify_from_db(db: &Pool<Sqlite>, user: &UserId) -> Option<Committee> {
|
|
||||||
sqlx::query_as::<_, Committee>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM committee
|
|
||||||
WHERE discord = ?
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*user.as_u64() as i64)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn save_to_db(db: &Pool<Sqlite>, email: &str, auth: &str, user: &UserId) -> Result<Option<Committee>, sqlx::Error> {
|
|
||||||
sqlx::query_as::<_, Committee>(
|
|
||||||
"
|
|
||||||
INSERT INTO committee (email, discord, auth_code)
|
|
||||||
VALUES (?1, ?2, ?3)
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(email.to_owned())
|
|
||||||
.bind(*user.as_u64() as i64)
|
|
||||||
.bind(auth.to_owned())
|
|
||||||
.fetch_optional(db)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod verify {
|
|
||||||
use super::*;
|
|
||||||
use crate::commands::committee::link::get_verify_from_db;
|
|
||||||
use serenity::model::id::{GuildId, RoleId};
|
|
||||||
use serenity::model::user::User;
|
|
||||||
use skynet_discord_bot::Committee;
|
|
||||||
use sqlx::Error;
|
|
||||||
|
|
||||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
|
||||||
let committee_server = GuildId(1220150752656363520);
|
|
||||||
match command.guild_id {
|
|
||||||
None => {
|
|
||||||
return "Not in correct discord server.".to_string();
|
|
||||||
}
|
|
||||||
Some(x) => {
|
|
||||||
if x != committee_server {
|
|
||||||
return "Not in correct discord server.".to_string();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let db_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let db = db_lock.read().await;
|
|
||||||
|
|
||||||
// check if user has used /link_committee
|
|
||||||
let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await {
|
|
||||||
x
|
|
||||||
} else {
|
|
||||||
return "Please use /link_committee first".to_string();
|
|
||||||
};
|
|
||||||
|
|
||||||
let option = command
|
|
||||||
.data
|
|
||||||
.options
|
|
||||||
.first()
|
|
||||||
.expect("Expected code option")
|
|
||||||
.resolved
|
|
||||||
.as_ref()
|
|
||||||
.expect("Expected code object");
|
|
||||||
|
|
||||||
let code = if let CommandDataOptionValue::String(code) = option {
|
|
||||||
code
|
|
||||||
} else {
|
|
||||||
return "Please provide a verification code".to_string();
|
|
||||||
};
|
|
||||||
|
|
||||||
if &details.auth_code != code {
|
|
||||||
return "Invalid verification code".to_string();
|
|
||||||
}
|
|
||||||
|
|
||||||
match set_discord(&db, &command.user.id).await {
|
|
||||||
Ok(_) => {
|
|
||||||
// get teh right roles for the user
|
|
||||||
set_server_roles(&command.user, ctx).await;
|
|
||||||
"Discord username linked to Wolves for committee".to_string()
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("{:?}", e);
|
|
||||||
"Failed to save, please try /link_committee again".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
|
||||||
command
|
|
||||||
.name("verify_committee")
|
|
||||||
.description("Verify Wolves Committee Email")
|
|
||||||
.create_option(|option| {
|
|
||||||
option
|
|
||||||
.name("code")
|
|
||||||
.description("Code from verification email")
|
|
||||||
.kind(CommandOptionType::String)
|
|
||||||
.required(true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_discord(db: &Pool<Sqlite>, discord: &UserId) -> Result<Option<Committee>, Error> {
|
|
||||||
sqlx::query_as::<_, Committee>(
|
|
||||||
"
|
|
||||||
UPDATE committee
|
|
||||||
SET committee = 1
|
|
||||||
WHERE discord = ?
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(*discord.as_u64() as i64)
|
|
||||||
.fetch_optional(db)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_server_roles(discord: &User, ctx: &Context) {
|
|
||||||
let committee_server = GuildId(1220150752656363520);
|
|
||||||
if let Ok(mut member) = committee_server.member(&ctx.http, &discord.id).await {
|
|
||||||
let committee_member = RoleId(1226602779968274573);
|
|
||||||
if let Err(e) = member.add_role(&ctx, committee_member).await {
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,10 +13,13 @@ use serenity::{
|
||||||
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use skynet_discord_bot::{get_now_iso, random_string, Config, DataBase, Wolves, WolvesVerify};
|
use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify};
|
||||||
|
use skynet_discord_bot::{get_now_iso, random_string, Config};
|
||||||
use sqlx::{Pool, Sqlite};
|
use sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
pub mod link {
|
pub mod link {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||||
let db_lock = {
|
let db_lock = {
|
||||||
|
@ -59,7 +62,32 @@ pub mod link {
|
||||||
// check if email exists
|
// check if email exists
|
||||||
let details = match get_server_member_email(&db, email).await {
|
let details = match get_server_member_email(&db, email).await {
|
||||||
None => {
|
None => {
|
||||||
return "Please check it matches (including case) your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string()
|
let invalid_user = "Please check it matches (including case) your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string();
|
||||||
|
|
||||||
|
// see if the user actually exists
|
||||||
|
let id = match get_user(&config, email).await {
|
||||||
|
None => {
|
||||||
|
return invalid_user;
|
||||||
|
}
|
||||||
|
Some(x) => x,
|
||||||
|
};
|
||||||
|
|
||||||
|
// save teh user id and email to teh db
|
||||||
|
match save_to_db_user(&db, id, email).await {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(x) => {
|
||||||
|
dbg!(x);
|
||||||
|
return "Error: unable to save user to teh database, contact Computer Society".to_string();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// pull it back out (technically could do it in previous step but more explicit)
|
||||||
|
match get_server_member_email(&db, email).await {
|
||||||
|
None => {
|
||||||
|
return "Error: failed to read user from database.".to_string();
|
||||||
|
}
|
||||||
|
Some(x) => x,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
};
|
};
|
||||||
|
@ -232,13 +260,68 @@ pub mod link {
|
||||||
.fetch_optional(db)
|
.fetch_optional(db)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum WolvesResultUserResult {
|
||||||
|
B(bool),
|
||||||
|
S(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct WolvesResultUser {
|
||||||
|
success: i64,
|
||||||
|
result: WolvesResultUserResult,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_user(config: &Config, email: &str) -> Option<i64> {
|
||||||
|
let url = format!("{}/get_id_from_email", &config.wolves_url);
|
||||||
|
match reqwest::Client::new()
|
||||||
|
.post(&url)
|
||||||
|
.form(&[("email", email)])
|
||||||
|
.header("X-AM-Identity", &config.wolves_api)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(x) => {
|
||||||
|
if let Ok(y) = x.json::<WolvesResultUser>().await {
|
||||||
|
// this is the only time we will get a positive response, the None at the end catches everything else
|
||||||
|
if let WolvesResultUserResult::S(z) = y.result {
|
||||||
|
if let Ok(id) = z.parse::<i64>() {
|
||||||
|
return Some(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn save_to_db_user(db: &Pool<Sqlite>, id_wolves: i64, email: &str) -> Result<Option<Wolves>, sqlx::Error> {
|
||||||
|
sqlx::query_as::<_, Wolves>(
|
||||||
|
"
|
||||||
|
INSERT INTO wolves (id_wolves, email)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
ON CONFLICT(id_wolves) DO UPDATE SET email = $2
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(id_wolves)
|
||||||
|
.bind(email)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod verify {
|
pub mod verify {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::commands::link_email::link::{db_pending_clear_expired, get_verify_from_db};
|
use crate::commands::link_email::link::{db_pending_clear_expired, get_verify_from_db};
|
||||||
use serenity::model::user::User;
|
use serenity::model::user::User;
|
||||||
use skynet_discord_bot::{get_server_config, ServerMembersWolves, Servers};
|
use skynet_discord_bot::common::database::get_server_config;
|
||||||
|
use skynet_discord_bot::common::database::{ServerMembersWolves, Servers};
|
||||||
use sqlx::Error;
|
use sqlx::Error;
|
||||||
|
|
||||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||||
|
|
|
@ -7,7 +7,7 @@ use serenity::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use skynet_discord_bot::DataBase;
|
use skynet_discord_bot::common::database::DataBase;
|
||||||
use sqlx::{Pool, Sqlite};
|
use sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
pub(crate) mod user {
|
pub(crate) mod user {
|
||||||
|
@ -16,7 +16,9 @@ pub(crate) mod user {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::commands::link_email::link::get_server_member_discord;
|
use crate::commands::link_email::link::get_server_member_discord;
|
||||||
use serenity::model::id::UserId;
|
use serenity::model::id::UserId;
|
||||||
use skynet_discord_bot::{whitelist_update, Config, Minecraft, Wolves};
|
use skynet_discord_bot::common::database::Wolves;
|
||||||
|
use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft};
|
||||||
|
use skynet_discord_bot::Config;
|
||||||
use sqlx::Error;
|
use sqlx::Error;
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||||
|
@ -122,7 +124,9 @@ pub(crate) mod server {
|
||||||
use sqlx::Error;
|
use sqlx::Error;
|
||||||
// this is to managfe the server side of commands related to minecraft
|
// this is to managfe the server side of commands related to minecraft
|
||||||
use super::*;
|
use super::*;
|
||||||
use skynet_discord_bot::{is_admin, update_server, Config, Minecraft};
|
use skynet_discord_bot::common::minecraft::update_server;
|
||||||
|
use skynet_discord_bot::common::minecraft::Minecraft;
|
||||||
|
use skynet_discord_bot::{is_admin, Config};
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||||
command.name("minecraft_add").description("Add a minecraft server").create_option(|option| {
|
command.name("minecraft_add").description("Add a minecraft server").create_option(|option| {
|
||||||
|
@ -201,7 +205,9 @@ pub(crate) mod server {
|
||||||
use serenity::builder::CreateApplicationCommand;
|
use serenity::builder::CreateApplicationCommand;
|
||||||
use serenity::client::Context;
|
use serenity::client::Context;
|
||||||
use serenity::model::prelude::application_command::ApplicationCommandInteraction;
|
use serenity::model::prelude::application_command::ApplicationCommandInteraction;
|
||||||
use skynet_discord_bot::{get_minecraft_config_server, is_admin, server_information, Config, DataBase};
|
use skynet_discord_bot::common::database::DataBase;
|
||||||
|
use skynet_discord_bot::common::minecraft::{get_minecraft_config_server, server_information};
|
||||||
|
use skynet_discord_bot::{is_admin, Config};
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||||
command.name("minecraft_list").description("List your minecraft servers")
|
command.name("minecraft_list").description("List your minecraft servers")
|
||||||
|
@ -262,7 +268,9 @@ pub(crate) mod server {
|
||||||
use serenity::model::application::command::CommandOptionType;
|
use serenity::model::application::command::CommandOptionType;
|
||||||
use serenity::model::id::GuildId;
|
use serenity::model::id::GuildId;
|
||||||
use serenity::model::prelude::application_command::{ApplicationCommandInteraction, CommandDataOptionValue};
|
use serenity::model::prelude::application_command::{ApplicationCommandInteraction, CommandDataOptionValue};
|
||||||
use skynet_discord_bot::{is_admin, DataBase, Minecraft};
|
use skynet_discord_bot::common::database::DataBase;
|
||||||
|
use skynet_discord_bot::common::minecraft::Minecraft;
|
||||||
|
use skynet_discord_bot::is_admin;
|
||||||
use sqlx::{Error, Pool, Sqlite};
|
use sqlx::{Error, Pool, Sqlite};
|
||||||
|
|
||||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
pub mod add_server;
|
pub mod add_server;
|
||||||
pub mod committee;
|
|
||||||
pub mod link_email;
|
pub mod link_email;
|
||||||
pub mod minecraft;
|
pub mod minecraft;
|
||||||
pub mod role_adder;
|
pub mod role_adder;
|
||||||
|
|
|
@ -7,7 +7,8 @@ use serenity::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use skynet_discord_bot::{is_admin, DataBase, RoleAdder};
|
use skynet_discord_bot::common::database::{DataBase, RoleAdder};
|
||||||
|
use skynet_discord_bot::is_admin;
|
||||||
use sqlx::{Error, Pool, Sqlite};
|
use sqlx::{Error, Pool, Sqlite};
|
||||||
|
|
||||||
pub mod edit {
|
pub mod edit {
|
||||||
|
@ -188,7 +189,7 @@ pub mod list {}
|
||||||
pub mod tools {
|
pub mod tools {
|
||||||
use serenity::client::Context;
|
use serenity::client::Context;
|
||||||
use serenity::model::guild::Member;
|
use serenity::model::guild::Member;
|
||||||
use skynet_discord_bot::RoleAdder;
|
use skynet_discord_bot::common::database::RoleAdder;
|
||||||
use sqlx::{Pool, Sqlite};
|
use sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
pub async fn on_role_change(db: &Pool<Sqlite>, ctx: &Context, mut new_data: Member) {
|
pub async fn on_role_change(db: &Pool<Sqlite>, ctx: &Context, mut new_data: Member) {
|
||||||
|
|
270
src/common/database.rs
Normal file
270
src/common/database.rs
Normal file
|
@ -0,0 +1,270 @@
|
||||||
|
use crate::Config;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serenity::model::guild;
|
||||||
|
use serenity::model::id::{ChannelId, GuildId, RoleId, UserId};
|
||||||
|
use serenity::prelude::TypeMapKey;
|
||||||
|
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow};
|
||||||
|
use sqlx::{Error, FromRow, Pool, Row, Sqlite};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
pub struct DataBase;
|
||||||
|
impl TypeMapKey for DataBase {
|
||||||
|
type Value = Arc<RwLock<Pool<Sqlite>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct ServerMembers {
|
||||||
|
pub server: GuildId,
|
||||||
|
pub id_wolves: i64,
|
||||||
|
pub expiry: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for ServerMembers {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let server_tmp: i64 = row.try_get("server")?;
|
||||||
|
let server = GuildId::from(server_tmp as u64);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
server,
|
||||||
|
id_wolves: row.try_get("id_wolves")?,
|
||||||
|
expiry: row.try_get("expiry")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct ServerMembersWolves {
|
||||||
|
pub server: GuildId,
|
||||||
|
pub id_wolves: i64,
|
||||||
|
pub expiry: String,
|
||||||
|
pub email: String,
|
||||||
|
pub discord: Option<UserId>,
|
||||||
|
pub minecraft: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let server_tmp: i64 = row.try_get("server")?;
|
||||||
|
let server = GuildId::from(server_tmp as u64);
|
||||||
|
let discord = match row.try_get("discord") {
|
||||||
|
Ok(x) => {
|
||||||
|
let tmp: i64 = x;
|
||||||
|
if tmp == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(UserId::from(tmp as u64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
server,
|
||||||
|
id_wolves: row.try_get("id_wolves")?,
|
||||||
|
expiry: row.try_get("expiry")?,
|
||||||
|
email: row.try_get("email")?,
|
||||||
|
discord,
|
||||||
|
minecraft: row.try_get("minecraft")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct Wolves {
|
||||||
|
pub id_wolves: i64,
|
||||||
|
pub email: String,
|
||||||
|
pub discord: Option<UserId>,
|
||||||
|
pub minecraft: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for Wolves {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let discord = match row.try_get("discord") {
|
||||||
|
Ok(x) => {
|
||||||
|
let tmp: i64 = x;
|
||||||
|
if tmp == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(UserId::from(tmp as u64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
id_wolves: row.try_get("id_wolves")?,
|
||||||
|
email: row.try_get("email")?,
|
||||||
|
discord,
|
||||||
|
minecraft: row.try_get("minecraft")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct WolvesVerify {
|
||||||
|
pub email: String,
|
||||||
|
pub discord: UserId,
|
||||||
|
pub auth_code: String,
|
||||||
|
pub date_expiry: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for WolvesVerify {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let user_tmp: i64 = row.try_get("discord")?;
|
||||||
|
let discord = UserId::from(user_tmp as u64);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
email: row.try_get("email")?,
|
||||||
|
discord,
|
||||||
|
auth_code: row.try_get("auth_code")?,
|
||||||
|
date_expiry: row.try_get("date_expiry")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct Servers {
|
||||||
|
pub server: GuildId,
|
||||||
|
pub wolves_api: String,
|
||||||
|
pub role_past: Option<RoleId>,
|
||||||
|
pub role_current: RoleId,
|
||||||
|
pub member_past: i64,
|
||||||
|
pub member_current: i64,
|
||||||
|
pub bot_channel_id: ChannelId,
|
||||||
|
// TODO: these can be removed in teh future with an API update
|
||||||
|
pub server_name: String,
|
||||||
|
pub wolves_link: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for Servers {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let server_tmp: i64 = row.try_get("server")?;
|
||||||
|
let server = GuildId::from(server_tmp as u64);
|
||||||
|
let role_past = match row.try_get("role_past") {
|
||||||
|
Ok(x) => {
|
||||||
|
let tmp: i64 = x;
|
||||||
|
if tmp == 0 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(RoleId::from(tmp as u64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let role_current = match row.try_get("role_current") {
|
||||||
|
Ok(x) => {
|
||||||
|
let tmp: i64 = x;
|
||||||
|
RoleId::from(tmp as u64)
|
||||||
|
}
|
||||||
|
_ => RoleId::from(0u64),
|
||||||
|
};
|
||||||
|
|
||||||
|
let bot_channel_tmp: i64 = row.try_get("bot_channel_id")?;
|
||||||
|
let bot_channel_id = ChannelId::from(bot_channel_tmp as u64);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
server,
|
||||||
|
wolves_api: row.try_get("wolves_api")?,
|
||||||
|
role_past,
|
||||||
|
role_current,
|
||||||
|
member_past: row.try_get("member_past")?,
|
||||||
|
member_current: row.try_get("member_current")?,
|
||||||
|
bot_channel_id,
|
||||||
|
server_name: row.try_get("server_name")?,
|
||||||
|
wolves_link: row.try_get("wolves_link")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct RoleAdder {
|
||||||
|
pub server: GuildId,
|
||||||
|
pub role_a: RoleId,
|
||||||
|
pub role_b: RoleId,
|
||||||
|
pub role_c: RoleId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for RoleAdder {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let server_tmp: i64 = row.try_get("server")?;
|
||||||
|
let server = GuildId::from(server_tmp as u64);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
server,
|
||||||
|
role_a: get_role_from_row(row, "role_a"),
|
||||||
|
role_b: get_role_from_row(row, "role_b"),
|
||||||
|
role_c: get_role_from_row(row, "role_c"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId {
|
||||||
|
match row.try_get(col) {
|
||||||
|
Ok(x) => {
|
||||||
|
let tmp: i64 = x;
|
||||||
|
RoleId(tmp as u64)
|
||||||
|
}
|
||||||
|
_ => RoleId::from(0u64),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn db_init(config: &Config) -> Result<Pool<Sqlite>, Error> {
|
||||||
|
let database = format!("{}/{}", &config.home, &config.database);
|
||||||
|
|
||||||
|
let pool = SqlitePoolOptions::new()
|
||||||
|
.max_connections(5)
|
||||||
|
.connect_with(
|
||||||
|
SqliteConnectOptions::from_str(&format!("sqlite://{}", database))?
|
||||||
|
.foreign_keys(true)
|
||||||
|
.create_if_missing(true),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// migrations are amazing!
|
||||||
|
sqlx::migrate!("./db/migrations").run(&pool).await?;
|
||||||
|
|
||||||
|
Ok(pool)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_config(db: &Pool<Sqlite>, server: &GuildId) -> Option<Servers> {
|
||||||
|
sqlx::query_as::<_, Servers>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM servers
|
||||||
|
WHERE server = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(*server.as_u64() as i64)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId, member: &guild::Member) -> Result<ServerMembersWolves, Error> {
|
||||||
|
sqlx::query_as::<_, ServerMembersWolves>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM server_members
|
||||||
|
JOIN wolves USING (id_wolves)
|
||||||
|
WHERE server = ? AND discord = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(*server.as_u64() as i64)
|
||||||
|
.bind(*member.user.id.as_u64() as i64)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_config_bulk(db: &Pool<Sqlite>) -> Vec<Servers> {
|
||||||
|
sqlx::query_as::<_, Servers>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM servers
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
164
src/common/minecraft.rs
Normal file
164
src/common/minecraft.rs
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
use crate::common::set_roles::normal::get_server_member_bulk;
|
||||||
|
use crate::Config;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serenity::model::id::GuildId;
|
||||||
|
use sqlx::sqlite::SqliteRow;
|
||||||
|
use sqlx::{Error, FromRow, Pool, Row, Sqlite};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct Minecraft {
|
||||||
|
pub discord: GuildId,
|
||||||
|
pub minecraft: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'r> FromRow<'r, SqliteRow> for Minecraft {
|
||||||
|
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||||
|
let server_tmp: i64 = row.try_get("server_discord")?;
|
||||||
|
let discord = GuildId::from(server_tmp as u64);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
discord,
|
||||||
|
minecraft: row.try_get("server_minecraft")?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
loop through all members of server
|
||||||
|
get a list of folks with mc accounts that are members
|
||||||
|
and a list that arent members
|
||||||
|
*/
|
||||||
|
pub async fn update_server(server_id: &str, db: &Pool<Sqlite>, g_id: &GuildId, config: &Config) {
|
||||||
|
let mut usernames = vec![];
|
||||||
|
for member in get_server_member_bulk(db, g_id).await {
|
||||||
|
if let Some(x) = member.minecraft {
|
||||||
|
usernames.push(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !usernames.is_empty() {
|
||||||
|
whitelist_update(&usernames, server_id, &config.discord_token_minecraft).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post<T: Serialize>(url: &str, bearer: &str, data: &T) {
|
||||||
|
match surf::post(url)
|
||||||
|
.header("Authorization", bearer)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Accept", "Application/vnd.pterodactyl.v1+json")
|
||||||
|
.body_json(&data)
|
||||||
|
{
|
||||||
|
Ok(req) => {
|
||||||
|
req.await.ok();
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct ServerDetailsResSub {
|
||||||
|
pub identifier: String,
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub is_suspended: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct ServerDetailsRes {
|
||||||
|
pub attributes: ServerDetailsResSub,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get<T: Serialize + DeserializeOwned>(url: &str, bearer: &str) -> Option<T> {
|
||||||
|
match surf::get(url)
|
||||||
|
.header("Authorization", bearer)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.header("Accept", "Application/vnd.pterodactyl.v1+json")
|
||||||
|
.recv_json()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(res) => Some(res),
|
||||||
|
Err(e) => {
|
||||||
|
dbg!(e);
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct BodyCommand {
|
||||||
|
command: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct BodyDelete {
|
||||||
|
root: String,
|
||||||
|
files: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn whitelist_update(add: &Vec<String>, server: &str, token: &str) {
|
||||||
|
let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}");
|
||||||
|
let bearer = format!("Bearer {token}");
|
||||||
|
|
||||||
|
for name in add {
|
||||||
|
let data = BodyCommand {
|
||||||
|
command: format!("whitelist add {name}"),
|
||||||
|
};
|
||||||
|
post(&format!("{url_base}/command"), &bearer, &data).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn whitelist_wipe(server: &str, token: &str) {
|
||||||
|
let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}");
|
||||||
|
let bearer = format!("Bearer {token}");
|
||||||
|
|
||||||
|
// delete whitelist
|
||||||
|
let deletion = BodyDelete {
|
||||||
|
root: "/".to_string(),
|
||||||
|
files: vec!["whitelist.json".to_string()],
|
||||||
|
};
|
||||||
|
post(&format!("{url_base}/files/delete"), &bearer, &deletion).await;
|
||||||
|
|
||||||
|
// recreate teh file, passing in the type here so the compiler knows what type of vec it is
|
||||||
|
post::<Vec<&str>>(&format!("{url_base}/files/write?file=%2Fwhitelist.json"), &bearer, &vec![]).await;
|
||||||
|
|
||||||
|
// reload the whitelist
|
||||||
|
let data = BodyCommand {
|
||||||
|
command: "whitelist reload".to_string(),
|
||||||
|
};
|
||||||
|
post(&format!("{url_base}/command"), &bearer, &data).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn server_information(server: &str, token: &str) -> Option<ServerDetailsRes> {
|
||||||
|
let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}");
|
||||||
|
let bearer = format!("Bearer {token}");
|
||||||
|
get::<ServerDetailsRes>(&format!("{url_base}/"), &bearer).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_minecraft_config(db: &Pool<Sqlite>) -> Vec<Minecraft> {
|
||||||
|
sqlx::query_as::<_, Minecraft>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM minecraft
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_minecraft_config_server(db: &Pool<Sqlite>, g_id: GuildId) -> Vec<Minecraft> {
|
||||||
|
sqlx::query_as::<_, Minecraft>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM minecraft
|
||||||
|
WHERE server_discord = ?1
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(*g_id.as_u64() as i64)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
4
src/common/mod.rs
Normal file
4
src/common/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod database;
|
||||||
|
pub mod minecraft;
|
||||||
|
pub mod set_roles;
|
||||||
|
pub mod wolves;
|
330
src/common/set_roles.rs
Normal file
330
src/common/set_roles.rs
Normal file
|
@ -0,0 +1,330 @@
|
||||||
|
pub mod normal {
|
||||||
|
use crate::common::database::{DataBase, ServerMembersWolves, Servers, Wolves};
|
||||||
|
use crate::get_now_iso;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::model::id::{GuildId, RoleId, UserId};
|
||||||
|
use sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
|
pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option<RoleId>], members_changed: &[UserId]) {
|
||||||
|
let db_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
|
let Servers {
|
||||||
|
server,
|
||||||
|
role_past,
|
||||||
|
role_current,
|
||||||
|
..
|
||||||
|
} = server;
|
||||||
|
|
||||||
|
let mut roles_set = [0, 0, 0];
|
||||||
|
let mut members = vec![];
|
||||||
|
|
||||||
|
for member in get_server_member_bulk(&db, server).await {
|
||||||
|
if let Some(x) = member.discord {
|
||||||
|
members.push(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let mut members_all = members.len();
|
||||||
|
|
||||||
|
if let Ok(x) = server.members(ctx, None, None).await {
|
||||||
|
for mut member in x {
|
||||||
|
// members_changed acts as an override to only deal with teh users in it
|
||||||
|
if !members_changed.is_empty() && !members_changed.contains(&member.user.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if members.contains(&member.user.id) {
|
||||||
|
let mut roles = vec![];
|
||||||
|
|
||||||
|
if let Some(role) = &role_past {
|
||||||
|
if !member.roles.contains(role) {
|
||||||
|
roles_set[0] += 1;
|
||||||
|
roles.push(role.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !member.roles.contains(role_current) {
|
||||||
|
roles_set[1] += 1;
|
||||||
|
roles.push(role_current.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(e) = member.add_roles(ctx, &roles).await {
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// old and never
|
||||||
|
|
||||||
|
if let Some(role) = &role_past {
|
||||||
|
if member.roles.contains(role) {
|
||||||
|
members_all += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if member.roles.contains(role_current) {
|
||||||
|
roles_set[2] += 1;
|
||||||
|
// if theya re not a current member and have the role then remove it
|
||||||
|
if let Err(e) = member.remove_role(ctx, role_current).await {
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for role in remove_roles.iter().flatten() {
|
||||||
|
if let Err(e) = member.remove_role(ctx, role).await {
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set_server_numbers(&db, server, members_all as i64, members.len() as i64).await;
|
||||||
|
|
||||||
|
// small bit of logging to note changes over time
|
||||||
|
println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.as_u64(), roles_set[0], roles_set[1], roles_set[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_member_bulk(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
|
||||||
|
sqlx::query_as::<_, ServerMembersWolves>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM server_members
|
||||||
|
JOIN wolves USING (id_wolves)
|
||||||
|
WHERE (
|
||||||
|
server = ?
|
||||||
|
AND discord IS NOT NULL
|
||||||
|
AND expiry > ?
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(*server.as_u64() as i64)
|
||||||
|
.bind(get_now_iso(true))
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn set_server_numbers(db: &Pool<Sqlite>, server: &GuildId, past: i64, current: i64) {
|
||||||
|
match sqlx::query_as::<_, Wolves>(
|
||||||
|
"
|
||||||
|
UPDATE servers
|
||||||
|
SET member_past = ?, member_current = ?
|
||||||
|
WHERE server = ?
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(past)
|
||||||
|
.bind(current)
|
||||||
|
.bind(*server.as_u64() as i64)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failure to insert into {}", server.as_u64());
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for updating committee members
|
||||||
|
pub mod committee {
|
||||||
|
use crate::common::database::{DataBase, Wolves};
|
||||||
|
use crate::common::wolves::committees::Committees;
|
||||||
|
use crate::Config;
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::model::channel::ChannelType;
|
||||||
|
use serenity::model::guild::Member;
|
||||||
|
use sqlx::{Pool, Sqlite};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
pub async fn check_committee(ctx: Arc<Context>) {
|
||||||
|
let db_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
|
let config_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
let config_global = config_lock.read().await;
|
||||||
|
|
||||||
|
let server = config_global.committee_server;
|
||||||
|
|
||||||
|
// because to use it to update a single user we need to pre-get the members of teh server
|
||||||
|
let mut members = server.members(&ctx, None, None).await.unwrap_or_default();
|
||||||
|
|
||||||
|
update_committees(&db, &ctx, &config_global, &mut members).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
This function can take a vec of members (or just one) and gives tehm the appropiate roles on teh committee server
|
||||||
|
*/
|
||||||
|
pub async fn update_committees(db: &Pool<Sqlite>, ctx: &Context, config: &Config, members: &mut Vec<Member>) {
|
||||||
|
let server = config.committee_server;
|
||||||
|
let committee_member = config.committee_role;
|
||||||
|
let committees = get_committees(db).await;
|
||||||
|
|
||||||
|
// information about the server
|
||||||
|
let roles = server.roles(&ctx).await.unwrap_or_default();
|
||||||
|
let channels = server.channels(&ctx).await.unwrap_or_default();
|
||||||
|
|
||||||
|
// make a hashmap of the nameof roles to quickly get them out again
|
||||||
|
let mut roles_name = HashMap::new();
|
||||||
|
for role in roles.values() {
|
||||||
|
roles_name.insert(role.name.to_owned(), role.to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut channels_name = HashMap::new();
|
||||||
|
for channel in channels.values() {
|
||||||
|
// we only care about teh channels in teh category
|
||||||
|
if let Some(x) = channel.parent_id {
|
||||||
|
if x.eq(&config.committee_category) {
|
||||||
|
channels_name.insert(channel.name.to_owned(), channel.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// a map of users and the roles they are goign to be getting
|
||||||
|
let mut users_roles = HashMap::new();
|
||||||
|
|
||||||
|
// a list of all the roles that can be removed from folks who should have them
|
||||||
|
let mut committee_roles = vec![committee_member];
|
||||||
|
|
||||||
|
for committee in &committees {
|
||||||
|
// get the role for this committee/club/soc
|
||||||
|
let role = match roles_name.get(&committee.name) {
|
||||||
|
Some(x) => Some(x.to_owned()),
|
||||||
|
None => {
|
||||||
|
// create teh role if it does not exist
|
||||||
|
match server.create_role(&ctx, |r| r.hoist(false).mentionable(true).name(&committee.name)).await {
|
||||||
|
Ok(x) => Some(x),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// create teh channel if it does nto exist
|
||||||
|
if !channels_name.contains_key(&committee.name) {
|
||||||
|
match server
|
||||||
|
.create_channel(&ctx, |c| c.name(&committee.name).kind(ChannelType::Text).category(config.committee_category))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(x) => {
|
||||||
|
// update teh channels name list
|
||||||
|
channels_name.insert(x.name.to_owned(), x.to_owned());
|
||||||
|
|
||||||
|
println!("Created channel: {}", &committee.name);
|
||||||
|
}
|
||||||
|
Err(x) => {
|
||||||
|
dbg!("Unable to create channel: ", x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// so if the role exists
|
||||||
|
if let Some(r) = role {
|
||||||
|
committee_roles.push(r.id);
|
||||||
|
|
||||||
|
for id_wolves in &committee.committee {
|
||||||
|
// ID in this is the wolves ID, so we need to get a matching discord ID (if one exists)
|
||||||
|
if let Some(x) = get_server_member_discord(db, id_wolves).await {
|
||||||
|
if let Some(member_tmp) = x.discord {
|
||||||
|
let values = users_roles.entry(member_tmp).or_insert(vec![]);
|
||||||
|
values.push(r.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we have a map of all users that should get roles time to go through all the folks on teh server
|
||||||
|
for member in members {
|
||||||
|
let roles_required = match users_roles.get(&member.user.id) {
|
||||||
|
None => {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
Some(x) => {
|
||||||
|
let mut combined = x.to_owned();
|
||||||
|
// this is the main role, since it provides access to everything.
|
||||||
|
combined.push(committee_member);
|
||||||
|
combined
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// get a list of all the roles to remove from someone
|
||||||
|
let mut roles_rem = vec![];
|
||||||
|
for role in &committee_roles {
|
||||||
|
if !roles_required.contains(role) {
|
||||||
|
roles_rem.push(role.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !roles_rem.is_empty() {
|
||||||
|
member.remove_roles(&ctx, &roles_rem).await.unwrap_or_default();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !roles_required.is_empty() {
|
||||||
|
// these roles are flavor roles, only there to make folks mentionable
|
||||||
|
member.add_roles(&ctx, &roles_required).await.unwrap_or_default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally re-order teh channels to make them visually apealing
|
||||||
|
let mut channel_names = channels_name.clone().into_keys().collect::<Vec<String>>();
|
||||||
|
channel_names.sort();
|
||||||
|
|
||||||
|
// get a list of all teh new positions
|
||||||
|
let mut new_positions = vec![];
|
||||||
|
for (i, name) in channel_names.iter().enumerate() {
|
||||||
|
if let Some(channel) = channels_name.get_mut(name) {
|
||||||
|
let position_new = i as u64;
|
||||||
|
if position_new != channel.position as u64 {
|
||||||
|
new_positions.push((channel.id.to_owned(), position_new));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !new_positions.is_empty() {
|
||||||
|
match server.reorder_channels(&ctx, new_positions).await {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("Successfully re-orderd the committee category");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
dbg!("Failed to re-order ", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_committees(db: &Pool<Sqlite>) -> Vec<Committees> {
|
||||||
|
sqlx::query_as::<_, Committees>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM committees
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_server_member_discord(db: &Pool<Sqlite>, user: &i64) -> Option<Wolves> {
|
||||||
|
sqlx::query_as::<_, Wolves>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM wolves
|
||||||
|
WHERE id_wolves = ?
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(user)
|
||||||
|
.fetch_one(db)
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
}
|
||||||
|
}
|
433
src/common/wolves.rs
Normal file
433
src/common/wolves.rs
Normal file
|
@ -0,0 +1,433 @@
|
||||||
|
use crate::common::database::Wolves;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
|
/**
|
||||||
|
This file relates to anything that directly interacts with teh wolves API
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct WolvesResultUserMin {
|
||||||
|
// committee: String,
|
||||||
|
member_id: String,
|
||||||
|
// first_name: String,
|
||||||
|
// last_name: String,
|
||||||
|
contact_email: String,
|
||||||
|
// opt_in_email: String,
|
||||||
|
// student_id: Option<String>,
|
||||||
|
// note: Option<String>,
|
||||||
|
// expiry: String,
|
||||||
|
// requested: String,
|
||||||
|
// approved: String,
|
||||||
|
// sitename: String,
|
||||||
|
// domain: String,
|
||||||
|
}
|
||||||
|
async fn add_users_wolves(db: &Pool<Sqlite>, user: &WolvesResultUserMin) {
|
||||||
|
// expiry
|
||||||
|
match sqlx::query_as::<_, Wolves>(
|
||||||
|
"
|
||||||
|
INSERT INTO wolves (id_wolves, email)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
ON CONFLICT(id_wolves) DO UPDATE SET email = $2
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(&user.member_id)
|
||||||
|
.bind(&user.contact_email)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failure to insert into Wolves {:?}", user);
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
This is getting data for Clubs and Socs
|
||||||
|
*/
|
||||||
|
pub mod cns {
|
||||||
|
use crate::common::database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers};
|
||||||
|
use crate::common::set_roles::normal::update_server;
|
||||||
|
use crate::common::wolves::{add_users_wolves, WolvesResultUserMin};
|
||||||
|
use crate::Config;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serenity::client::Context;
|
||||||
|
use serenity::model::id::GuildId;
|
||||||
|
use sqlx::{Pool, Sqlite};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct WolvesResultUser {
|
||||||
|
// TODO: Might be worth trying to get this replaced with the club/soc ID?
|
||||||
|
committee: String,
|
||||||
|
member_id: String,
|
||||||
|
first_name: String,
|
||||||
|
last_name: String,
|
||||||
|
contact_email: String,
|
||||||
|
opt_in_email: String,
|
||||||
|
student_id: Option<String>,
|
||||||
|
note: Option<String>,
|
||||||
|
expiry: String,
|
||||||
|
requested: String,
|
||||||
|
approved: String,
|
||||||
|
sitename: String,
|
||||||
|
domain: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&WolvesResultUser> for WolvesResultUserMin {
|
||||||
|
fn from(value: &WolvesResultUser) -> Self {
|
||||||
|
Self {
|
||||||
|
member_id: value.member_id.to_owned(),
|
||||||
|
contact_email: value.contact_email.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct WolvesResult {
|
||||||
|
success: i8,
|
||||||
|
result: Vec<WolvesResultUser>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct WolvesResultLocal {
|
||||||
|
pub id_wolves: String,
|
||||||
|
pub email: String,
|
||||||
|
pub expiry: String,
|
||||||
|
}
|
||||||
|
pub async fn get_wolves(ctx: &Context) {
|
||||||
|
let db_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
|
let config_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
let config = config_lock.read().await;
|
||||||
|
|
||||||
|
for server_config in get_server_config_bulk(&db).await {
|
||||||
|
let Servers {
|
||||||
|
server,
|
||||||
|
wolves_api,
|
||||||
|
..
|
||||||
|
} = &server_config;
|
||||||
|
|
||||||
|
let existing_tmp = get_server_member(&db, server).await;
|
||||||
|
let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::<BTreeMap<_, _>>();
|
||||||
|
|
||||||
|
// list of users that need to be updated for this server
|
||||||
|
let mut user_to_update = vec![];
|
||||||
|
for user in get_wolves_sub(&config, wolves_api).await {
|
||||||
|
let id = user.member_id.parse::<u64>().unwrap_or_default();
|
||||||
|
match existing.get(&(id as i64)) {
|
||||||
|
None => {
|
||||||
|
// user does not exist already, add everything
|
||||||
|
add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await;
|
||||||
|
add_users_server_members(&db, server, &user).await;
|
||||||
|
}
|
||||||
|
Some(old) => {
|
||||||
|
// always update wolves table, in case data has changed
|
||||||
|
add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await;
|
||||||
|
if old.expiry != user.expiry {
|
||||||
|
add_users_server_members(&db, server, &user).await;
|
||||||
|
|
||||||
|
if let Some(discord_id) = old.discord {
|
||||||
|
user_to_update.push(discord_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !user_to_update.is_empty() {
|
||||||
|
update_server(ctx, &server_config, &[], &user_to_update).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
|
||||||
|
sqlx::query_as::<_, ServerMembersWolves>(
|
||||||
|
r#"
|
||||||
|
SELECT *
|
||||||
|
FROM server_members
|
||||||
|
JOIN wolves USING (id_wolves)
|
||||||
|
WHERE (
|
||||||
|
server = ?
|
||||||
|
AND discord IS NOT NULL
|
||||||
|
)
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.bind(*server.as_u64() as i64)
|
||||||
|
.fetch_all(db)
|
||||||
|
.await
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_wolves_sub(config: &Config, wolves_api: &str) -> Vec<WolvesResultUser> {
|
||||||
|
if config.wolves_url.is_empty() {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = format!("{}/get_members", &config.wolves_url);
|
||||||
|
|
||||||
|
// get wolves data
|
||||||
|
if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await {
|
||||||
|
if let Ok(WolvesResult {
|
||||||
|
success,
|
||||||
|
result,
|
||||||
|
}) = res.body_json().await
|
||||||
|
{
|
||||||
|
if success != 1 {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_users_server_members(db: &Pool<Sqlite>, server: &GuildId, user: &WolvesResultUser) {
|
||||||
|
match sqlx::query_as::<_, ServerMembers>(
|
||||||
|
"
|
||||||
|
INSERT OR REPLACE INTO server_members (server, id_wolves, expiry)
|
||||||
|
VALUES (?1, ?2, ?3)
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(*server.as_u64() as i64)
|
||||||
|
.bind(&user.member_id)
|
||||||
|
.bind(&user.expiry)
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failure to insert into ServerMembers {} {:?}", server.as_u64(), user);
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Get and store the data on C&S committees
|
||||||
|
*/
|
||||||
|
pub mod committees {
|
||||||
|
use crate::common::database::DataBase;
|
||||||
|
use crate::Config;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serenity::client::Context;
|
||||||
|
use sqlx::{Pool, Sqlite};
|
||||||
|
|
||||||
|
// This is what Wolves returns to us
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct WolvesResult {
|
||||||
|
success: i8,
|
||||||
|
result: Vec<WolvesResultCNS>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is teh actual data we care about
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct WolvesResultCNS {
|
||||||
|
id: String,
|
||||||
|
name: String,
|
||||||
|
// Link to their page such as https://ulwolves.ie/society/computer
|
||||||
|
link: String,
|
||||||
|
// array of Committee members member_id's
|
||||||
|
committee: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Database entry for it
|
||||||
|
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||||
|
pub struct Committees {
|
||||||
|
pub id: i64,
|
||||||
|
pub name: String,
|
||||||
|
pub link: String,
|
||||||
|
#[sqlx(json)]
|
||||||
|
pub committee: Vec<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<WolvesResultCNS> for Committees {
|
||||||
|
fn from(value: WolvesResultCNS) -> Self {
|
||||||
|
Self {
|
||||||
|
id: value.id.parse().unwrap_or(0),
|
||||||
|
name: value.name,
|
||||||
|
link: value.link,
|
||||||
|
committee: value.committee.iter().map(|x| x.parse::<i64>().unwrap_or(0)).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_cns(ctx: &Context) {
|
||||||
|
let db_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
|
let config_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
let config = config_lock.read().await;
|
||||||
|
|
||||||
|
// request data from wolves
|
||||||
|
for committee_wolves in get_committees(&config).await {
|
||||||
|
let committee = Committees::from(committee_wolves);
|
||||||
|
add_committee(&db, &committee).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_committees(config: &Config) -> Vec<WolvesResultCNS> {
|
||||||
|
if config.wolves_url.is_empty() {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Change teh stored env value to teh base domain
|
||||||
|
// TODO: this address may change
|
||||||
|
let url = format!("{}/get_cns", &config.wolves_url);
|
||||||
|
|
||||||
|
// get wolves data
|
||||||
|
if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", &config.wolves_api).await {
|
||||||
|
if let Ok(WolvesResult {
|
||||||
|
success,
|
||||||
|
result,
|
||||||
|
}) = res.body_json().await
|
||||||
|
{
|
||||||
|
if success != 1 {
|
||||||
|
return vec![];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_committee(db: &Pool<Sqlite>, committee: &Committees) {
|
||||||
|
match sqlx::query_as::<_, Committees>(
|
||||||
|
"
|
||||||
|
INSERT INTO committees (id, name, link, committee)
|
||||||
|
VALUES ($1, $2, $3, $4)
|
||||||
|
ON CONFLICT(id) DO UPDATE SET committee = $4
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.bind(committee.id)
|
||||||
|
.bind(&committee.name)
|
||||||
|
.bind(&committee.link)
|
||||||
|
.bind(serde_json::to_string(&committee.committee).unwrap_or_default())
|
||||||
|
.fetch_optional(db)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Failure to insert into Committees {:?}", committee);
|
||||||
|
println!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
get the data for an individual user
|
||||||
|
*/
|
||||||
|
pub mod individual {
|
||||||
|
use crate::common::database::DataBase;
|
||||||
|
use crate::common::wolves::{add_users_wolves, WolvesResultUserMin};
|
||||||
|
use crate::Config;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serenity::client::Context;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct WolvesResultUser {
|
||||||
|
// committee: String,
|
||||||
|
member_id: String,
|
||||||
|
// first_name: String,
|
||||||
|
// last_name: String,
|
||||||
|
contact_email: String,
|
||||||
|
// opt_in_email: String,
|
||||||
|
// student_id: Option<String>,
|
||||||
|
// note: Option<String>,
|
||||||
|
// expiry: String,
|
||||||
|
// requested: String,
|
||||||
|
// approved: String,
|
||||||
|
// sitename: String,
|
||||||
|
// domain: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&WolvesResultUser> for WolvesResultUserMin {
|
||||||
|
fn from(value: &WolvesResultUser) -> Self {
|
||||||
|
Self {
|
||||||
|
member_id: value.member_id.to_owned(),
|
||||||
|
contact_email: value.contact_email.to_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
struct WolvesResult {
|
||||||
|
success: i8,
|
||||||
|
result: WolvesResultUser,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user(ctx: &Context, email: &str) -> bool {
|
||||||
|
let db_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
let db = db_lock.read().await;
|
||||||
|
|
||||||
|
let config_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
let config = config_lock.read().await;
|
||||||
|
|
||||||
|
// TODO: proper api key management
|
||||||
|
let api_key = "";
|
||||||
|
// request data from wolves
|
||||||
|
match get_user_sub(&config, api_key, email).await {
|
||||||
|
None => false,
|
||||||
|
// if exists save it and return true
|
||||||
|
Some(user) => {
|
||||||
|
// add to db
|
||||||
|
add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await;
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_user_sub(config: &Config, wolves_api: &str, _email: &str) -> Option<WolvesResultUser> {
|
||||||
|
if config.wolves_url.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Change teh stored env value to teh base domain
|
||||||
|
let url = format!("{}/get_member", &config.wolves_url);
|
||||||
|
|
||||||
|
// get wolves data
|
||||||
|
if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await {
|
||||||
|
if let Ok(WolvesResult {
|
||||||
|
success,
|
||||||
|
result,
|
||||||
|
}) = res.body_json().await
|
||||||
|
{
|
||||||
|
if success != 1 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
783
src/lib.rs
783
src/lib.rs
|
@ -1,27 +1,14 @@
|
||||||
use dotenvy::dotenv;
|
pub mod common;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serenity::{
|
|
||||||
model::{
|
|
||||||
guild,
|
|
||||||
id::{ChannelId, GuildId, RoleId},
|
|
||||||
},
|
|
||||||
prelude::TypeMapKey,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::set_roles::get_server_member_bulk;
|
|
||||||
use chrono::{Datelike, SecondsFormat, Utc};
|
use chrono::{Datelike, SecondsFormat, Utc};
|
||||||
|
use dotenvy::dotenv;
|
||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||||
use serde::de::DeserializeOwned;
|
|
||||||
use serenity::client::Context;
|
use serenity::client::Context;
|
||||||
use serenity::model::id::UserId;
|
use serenity::model::id::{ChannelId, GuildId, RoleId};
|
||||||
use serenity::model::prelude::application_command::ApplicationCommandInteraction;
|
use serenity::model::prelude::application_command::ApplicationCommandInteraction;
|
||||||
use sqlx::{
|
use serenity::prelude::TypeMapKey;
|
||||||
sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow},
|
use std::{env, sync::Arc};
|
||||||
Error, FromRow, Pool, Row, Sqlite,
|
|
||||||
};
|
|
||||||
use std::{env, str::FromStr, sync::Arc};
|
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
// manages where teh database is stored
|
// manages where teh database is stored
|
||||||
pub home: String,
|
pub home: String,
|
||||||
|
@ -38,16 +25,18 @@ pub struct Config {
|
||||||
|
|
||||||
// wolves API base for clubs/socs
|
// wolves API base for clubs/socs
|
||||||
pub wolves_url: String,
|
pub wolves_url: String,
|
||||||
|
// API key for accessing more general resources
|
||||||
|
pub wolves_api: String,
|
||||||
|
|
||||||
|
// discord server for committee
|
||||||
|
pub committee_server: GuildId,
|
||||||
|
pub committee_role: RoleId,
|
||||||
|
pub committee_category: ChannelId,
|
||||||
}
|
}
|
||||||
impl TypeMapKey for Config {
|
impl TypeMapKey for Config {
|
||||||
type Value = Arc<RwLock<Config>>;
|
type Value = Arc<RwLock<Config>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DataBase;
|
|
||||||
impl TypeMapKey for DataBase {
|
|
||||||
type Value = Arc<RwLock<Pool<Sqlite>>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_config() -> Config {
|
pub fn get_config() -> Config {
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|
||||||
|
@ -63,6 +52,10 @@ pub fn get_config() -> Config {
|
||||||
mail_user: "".to_string(),
|
mail_user: "".to_string(),
|
||||||
mail_pass: "".to_string(),
|
mail_pass: "".to_string(),
|
||||||
wolves_url: "".to_string(),
|
wolves_url: "".to_string(),
|
||||||
|
wolves_api: "".to_string(),
|
||||||
|
committee_server: GuildId(0),
|
||||||
|
committee_role: RoleId(0),
|
||||||
|
committee_category: ChannelId(0),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(x) = env::var("DATABASE_HOME") {
|
if let Ok(x) = env::var("DATABASE_HOME") {
|
||||||
|
@ -89,300 +82,33 @@ pub fn get_config() -> Config {
|
||||||
config.mail_pass = x.trim().to_string();
|
config.mail_pass = x.trim().to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(x) = env::var("WOLVES_URL") {
|
if let Ok(x) = env::var("WOLVES_URL_BASE") {
|
||||||
config.wolves_url = x.trim().to_string();
|
config.wolves_url = x.trim().to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(x) = env::var("WOLVES_API") {
|
||||||
|
config.wolves_api = x.trim().to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(x) = env::var("COMMITTEE_DISCORD") {
|
||||||
|
if let Ok(x) = x.trim().parse::<u64>() {
|
||||||
|
config.committee_server = GuildId(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(x) = env::var("COMMITTEE_DISCORD") {
|
||||||
|
if let Ok(x) = x.trim().parse::<u64>() {
|
||||||
|
config.committee_role = RoleId(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(x) = env::var("COMMITTEE_CATEGORY") {
|
||||||
|
if let Ok(x) = x.trim().parse::<u64>() {
|
||||||
|
config.committee_category = ChannelId(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct ServerMembers {
|
|
||||||
pub server: GuildId,
|
|
||||||
pub id_wolves: i64,
|
|
||||||
pub expiry: String,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for ServerMembers {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let server_tmp: i64 = row.try_get("server")?;
|
|
||||||
let server = GuildId::from(server_tmp as u64);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
server,
|
|
||||||
id_wolves: row.try_get("id_wolves")?,
|
|
||||||
expiry: row.try_get("expiry")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct ServerMembersWolves {
|
|
||||||
pub server: GuildId,
|
|
||||||
pub id_wolves: i64,
|
|
||||||
pub expiry: String,
|
|
||||||
pub email: String,
|
|
||||||
pub discord: Option<UserId>,
|
|
||||||
pub minecraft: Option<String>,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let server_tmp: i64 = row.try_get("server")?;
|
|
||||||
let server = GuildId::from(server_tmp as u64);
|
|
||||||
let discord = match row.try_get("discord") {
|
|
||||||
Ok(x) => {
|
|
||||||
let tmp: i64 = x;
|
|
||||||
if tmp == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(UserId::from(tmp as u64))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
server,
|
|
||||||
id_wolves: row.try_get("id_wolves")?,
|
|
||||||
expiry: row.try_get("expiry")?,
|
|
||||||
email: row.try_get("email")?,
|
|
||||||
discord,
|
|
||||||
minecraft: row.try_get("minecraft")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct Wolves {
|
|
||||||
pub id_wolves: i64,
|
|
||||||
pub email: String,
|
|
||||||
pub discord: Option<UserId>,
|
|
||||||
pub minecraft: Option<String>,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for Wolves {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let discord = match row.try_get("discord") {
|
|
||||||
Ok(x) => {
|
|
||||||
let tmp: i64 = x;
|
|
||||||
if tmp == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(UserId::from(tmp as u64))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
id_wolves: row.try_get("id_wolves")?,
|
|
||||||
email: row.try_get("email")?,
|
|
||||||
discord,
|
|
||||||
minecraft: row.try_get("minecraft")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct WolvesVerify {
|
|
||||||
pub email: String,
|
|
||||||
pub discord: UserId,
|
|
||||||
pub auth_code: String,
|
|
||||||
pub date_expiry: String,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for WolvesVerify {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let user_tmp: i64 = row.try_get("discord")?;
|
|
||||||
let discord = UserId::from(user_tmp as u64);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
email: row.try_get("email")?,
|
|
||||||
discord,
|
|
||||||
auth_code: row.try_get("auth_code")?,
|
|
||||||
date_expiry: row.try_get("date_expiry")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct Committee {
|
|
||||||
pub email: String,
|
|
||||||
pub discord: UserId,
|
|
||||||
pub auth_code: String,
|
|
||||||
pub committee: i64,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for Committee {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let user_tmp: i64 = row.try_get("discord")?;
|
|
||||||
let discord = UserId::from(user_tmp as u64);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
email: row.try_get("email")?,
|
|
||||||
discord,
|
|
||||||
auth_code: row.try_get("auth_code")?,
|
|
||||||
committee: row.try_get("committee")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct Servers {
|
|
||||||
pub server: GuildId,
|
|
||||||
pub wolves_api: String,
|
|
||||||
pub role_past: Option<RoleId>,
|
|
||||||
pub role_current: RoleId,
|
|
||||||
pub member_past: i64,
|
|
||||||
pub member_current: i64,
|
|
||||||
pub bot_channel_id: ChannelId,
|
|
||||||
// these can be removed in teh future with an API update
|
|
||||||
pub server_name: String,
|
|
||||||
pub wolves_link: String,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for Servers {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let server_tmp: i64 = row.try_get("server")?;
|
|
||||||
let server = GuildId::from(server_tmp as u64);
|
|
||||||
let role_past = match row.try_get("role_past") {
|
|
||||||
Ok(x) => {
|
|
||||||
let tmp: i64 = x;
|
|
||||||
if tmp == 0 {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(RoleId::from(tmp as u64))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
let role_current = match row.try_get("role_current") {
|
|
||||||
Ok(x) => {
|
|
||||||
let tmp: i64 = x;
|
|
||||||
RoleId::from(tmp as u64)
|
|
||||||
}
|
|
||||||
_ => RoleId::from(0u64),
|
|
||||||
};
|
|
||||||
|
|
||||||
let bot_channel_tmp: i64 = row.try_get("bot_channel_id")?;
|
|
||||||
let bot_channel_id = ChannelId::from(bot_channel_tmp as u64);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
server,
|
|
||||||
wolves_api: row.try_get("wolves_api")?,
|
|
||||||
role_past,
|
|
||||||
role_current,
|
|
||||||
member_past: row.try_get("member_past")?,
|
|
||||||
member_current: row.try_get("member_current")?,
|
|
||||||
bot_channel_id,
|
|
||||||
server_name: row.try_get("server_name")?,
|
|
||||||
wolves_link: row.try_get("wolves_link")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct Minecraft {
|
|
||||||
pub discord: GuildId,
|
|
||||||
pub minecraft: String,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for Minecraft {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let server_tmp: i64 = row.try_get("server_discord")?;
|
|
||||||
let discord = GuildId::from(server_tmp as u64);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
discord,
|
|
||||||
minecraft: row.try_get("server_minecraft")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
|
||||||
pub struct RoleAdder {
|
|
||||||
pub server: GuildId,
|
|
||||||
pub role_a: RoleId,
|
|
||||||
pub role_b: RoleId,
|
|
||||||
pub role_c: RoleId,
|
|
||||||
}
|
|
||||||
impl<'r> FromRow<'r, SqliteRow> for RoleAdder {
|
|
||||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
|
||||||
let server_tmp: i64 = row.try_get("server")?;
|
|
||||||
let server = GuildId::from(server_tmp as u64);
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
server,
|
|
||||||
role_a: get_role_from_row(row, "role_a"),
|
|
||||||
role_b: get_role_from_row(row, "role_b"),
|
|
||||||
role_c: get_role_from_row(row, "role_c"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId {
|
|
||||||
match row.try_get(col) {
|
|
||||||
Ok(x) => {
|
|
||||||
let tmp: i64 = x;
|
|
||||||
RoleId(tmp as u64)
|
|
||||||
}
|
|
||||||
_ => RoleId::from(0u64),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn db_init(config: &Config) -> Result<Pool<Sqlite>, Error> {
|
|
||||||
let database = format!("{}/{}", &config.home, &config.database);
|
|
||||||
|
|
||||||
let pool = SqlitePoolOptions::new()
|
|
||||||
.max_connections(5)
|
|
||||||
.connect_with(
|
|
||||||
SqliteConnectOptions::from_str(&format!("sqlite://{}", database))?
|
|
||||||
.foreign_keys(true)
|
|
||||||
.create_if_missing(true),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// migrations are amazing!
|
|
||||||
sqlx::migrate!("./db/migrations").run(&pool).await?;
|
|
||||||
|
|
||||||
Ok(pool)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_config(db: &Pool<Sqlite>, server: &GuildId) -> Option<Servers> {
|
|
||||||
sqlx::query_as::<_, Servers>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM servers
|
|
||||||
WHERE server = ?
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*server.as_u64() as i64)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId, member: &guild::Member) -> Result<ServerMembersWolves, Error> {
|
|
||||||
sqlx::query_as::<_, ServerMembersWolves>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM server_members
|
|
||||||
JOIN wolves USING (id_wolves)
|
|
||||||
WHERE server = ? AND discord = ?
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*server.as_u64() as i64)
|
|
||||||
.bind(*member.user.id.as_u64() as i64)
|
|
||||||
.fetch_one(db)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_config_bulk(db: &Pool<Sqlite>) -> Vec<Servers> {
|
|
||||||
sqlx::query_as::<_, Servers>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM servers
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.fetch_all(db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_now_iso(short: bool) -> String {
|
pub fn get_now_iso(short: bool) -> String {
|
||||||
let now = Utc::now();
|
let now = Utc::now();
|
||||||
if short {
|
if short {
|
||||||
|
@ -396,303 +122,6 @@ pub fn random_string(len: usize) -> String {
|
||||||
thread_rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect()
|
thread_rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod set_roles {
|
|
||||||
use super::*;
|
|
||||||
pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option<RoleId>], members_changed: &[UserId]) {
|
|
||||||
let db_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
|
|
||||||
let db = db_lock.read().await;
|
|
||||||
|
|
||||||
let Servers {
|
|
||||||
server,
|
|
||||||
role_past,
|
|
||||||
role_current,
|
|
||||||
..
|
|
||||||
} = server;
|
|
||||||
|
|
||||||
let mut roles_set = [0, 0, 0];
|
|
||||||
let mut members = vec![];
|
|
||||||
|
|
||||||
for member in get_server_member_bulk(&db, server).await {
|
|
||||||
if let Some(x) = member.discord {
|
|
||||||
members.push(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut members_all = members.len();
|
|
||||||
|
|
||||||
if let Ok(x) = server.members(ctx, None, None).await {
|
|
||||||
for mut member in x {
|
|
||||||
// members_changed acts as an override to only deal with teh users in it
|
|
||||||
if !members_changed.is_empty() && !members_changed.contains(&member.user.id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if members.contains(&member.user.id) {
|
|
||||||
let mut roles = vec![];
|
|
||||||
|
|
||||||
if let Some(role) = &role_past {
|
|
||||||
if !member.roles.contains(role) {
|
|
||||||
roles_set[0] += 1;
|
|
||||||
roles.push(role.to_owned());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !member.roles.contains(role_current) {
|
|
||||||
roles_set[1] += 1;
|
|
||||||
roles.push(role_current.to_owned());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = member.add_roles(ctx, &roles).await {
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// old and never
|
|
||||||
|
|
||||||
if let Some(role) = &role_past {
|
|
||||||
if member.roles.contains(role) {
|
|
||||||
members_all += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if member.roles.contains(role_current) {
|
|
||||||
roles_set[2] += 1;
|
|
||||||
// if theya re not a current member and have the role then remove it
|
|
||||||
if let Err(e) = member.remove_role(ctx, role_current).await {
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for role in remove_roles.iter().flatten() {
|
|
||||||
if let Err(e) = member.remove_role(ctx, role).await {
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set_server_numbers(&db, server, members_all as i64, members.len() as i64).await;
|
|
||||||
|
|
||||||
// small bit of logging to note changes over time
|
|
||||||
println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.as_u64(), roles_set[0], roles_set[1], roles_set[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_member_bulk(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
|
|
||||||
sqlx::query_as::<_, ServerMembersWolves>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM server_members
|
|
||||||
JOIN wolves USING (id_wolves)
|
|
||||||
WHERE (
|
|
||||||
server = ?
|
|
||||||
AND discord IS NOT NULL
|
|
||||||
AND expiry > ?
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*server.as_u64() as i64)
|
|
||||||
.bind(get_now_iso(true))
|
|
||||||
.fetch_all(db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn set_server_numbers(db: &Pool<Sqlite>, server: &GuildId, past: i64, current: i64) {
|
|
||||||
match sqlx::query_as::<_, Wolves>(
|
|
||||||
"
|
|
||||||
UPDATE servers
|
|
||||||
SET member_past = ?, member_current = ?
|
|
||||||
WHERE server = ?
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(past)
|
|
||||||
.bind(current)
|
|
||||||
.bind(*server.as_u64() as i64)
|
|
||||||
.fetch_optional(db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
println!("Failure to insert into {}", server.as_u64());
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod get_data {
|
|
||||||
use super::*;
|
|
||||||
use crate::set_roles::update_server;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
struct WolvesResultUser {
|
|
||||||
committee: String,
|
|
||||||
member_id: String,
|
|
||||||
first_name: String,
|
|
||||||
last_name: String,
|
|
||||||
contact_email: String,
|
|
||||||
opt_in_email: String,
|
|
||||||
student_id: Option<String>,
|
|
||||||
note: Option<String>,
|
|
||||||
expiry: String,
|
|
||||||
requested: String,
|
|
||||||
approved: String,
|
|
||||||
sitename: String,
|
|
||||||
domain: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
struct WolvesResult {
|
|
||||||
success: i8,
|
|
||||||
result: Vec<WolvesResultUser>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
struct WolvesResultLocal {
|
|
||||||
pub id_wolves: String,
|
|
||||||
pub email: String,
|
|
||||||
pub expiry: String,
|
|
||||||
}
|
|
||||||
pub async fn get_wolves(ctx: &Context) {
|
|
||||||
let db_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let db = db_lock.read().await;
|
|
||||||
|
|
||||||
let config_lock = {
|
|
||||||
let data_read = ctx.data.read().await;
|
|
||||||
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
|
||||||
};
|
|
||||||
let config = config_lock.read().await;
|
|
||||||
|
|
||||||
for server_config in get_server_config_bulk(&db).await {
|
|
||||||
let Servers {
|
|
||||||
server,
|
|
||||||
wolves_api,
|
|
||||||
..
|
|
||||||
} = &server_config;
|
|
||||||
|
|
||||||
let existing_tmp = get_server_member(&db, server).await;
|
|
||||||
let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::<BTreeMap<_, _>>();
|
|
||||||
|
|
||||||
// list of users that need to be updated for this server
|
|
||||||
let mut user_to_update = vec![];
|
|
||||||
for user in get_wolves_sub(&config, wolves_api).await {
|
|
||||||
let id = user.member_id.parse::<u64>().unwrap_or_default();
|
|
||||||
match existing.get(&(id as i64)) {
|
|
||||||
None => {
|
|
||||||
// user does not exist already, add everything
|
|
||||||
add_users_wolves(&db, &user).await;
|
|
||||||
add_users_server_members(&db, server, &user).await;
|
|
||||||
}
|
|
||||||
Some(old) => {
|
|
||||||
// always update wolves table, in case data has changed
|
|
||||||
add_users_wolves(&db, &user).await;
|
|
||||||
if old.expiry != user.expiry {
|
|
||||||
add_users_server_members(&db, server, &user).await;
|
|
||||||
|
|
||||||
if let Some(discord_id) = old.discord {
|
|
||||||
user_to_update.push(discord_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !user_to_update.is_empty() {
|
|
||||||
update_server(ctx, &server_config, &[], &user_to_update).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
|
|
||||||
sqlx::query_as::<_, ServerMembersWolves>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM server_members
|
|
||||||
JOIN wolves USING (id_wolves)
|
|
||||||
WHERE (
|
|
||||||
server = ?
|
|
||||||
AND discord IS NOT NULL
|
|
||||||
)
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*server.as_u64() as i64)
|
|
||||||
.fetch_all(db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_wolves_sub(config: &Config, wolves_api: &str) -> Vec<WolvesResultUser> {
|
|
||||||
if config.wolves_url.is_empty() {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
// get wolves data
|
|
||||||
if let Ok(mut res) = surf::post(&config.wolves_url).header("X-AM-Identity", wolves_api).await {
|
|
||||||
if let Ok(WolvesResult {
|
|
||||||
success,
|
|
||||||
result,
|
|
||||||
}) = res.body_json().await
|
|
||||||
{
|
|
||||||
if success != 1 {
|
|
||||||
return vec![];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn add_users_wolves(db: &Pool<Sqlite>, user: &WolvesResultUser) {
|
|
||||||
// expiry
|
|
||||||
match sqlx::query_as::<_, Wolves>(
|
|
||||||
"
|
|
||||||
INSERT INTO wolves (id_wolves, email)
|
|
||||||
VALUES ($1, $2)
|
|
||||||
ON CONFLICT(id_wolves) DO UPDATE SET email = $2
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(&user.member_id)
|
|
||||||
.bind(&user.contact_email)
|
|
||||||
.fetch_optional(db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
println!("Failure to insert into Wolves {:?}", user);
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async fn add_users_server_members(db: &Pool<Sqlite>, server: &GuildId, user: &WolvesResultUser) {
|
|
||||||
match sqlx::query_as::<_, ServerMembers>(
|
|
||||||
"
|
|
||||||
INSERT OR REPLACE INTO server_members (server, id_wolves, expiry)
|
|
||||||
VALUES (?1, ?2, ?3)
|
|
||||||
",
|
|
||||||
)
|
|
||||||
.bind(*server.as_u64() as i64)
|
|
||||||
.bind(&user.member_id)
|
|
||||||
.bind(&user.expiry)
|
|
||||||
.fetch_optional(db)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
println!("Failure to insert into ServerMembers {} {:?}", server.as_u64(), user);
|
|
||||||
println!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
For any time ye need to check if a user who calls a command has admin privlages
|
For any time ye need to check if a user who calls a command has admin privlages
|
||||||
*/
|
*/
|
||||||
|
@ -730,141 +159,3 @@ pub async fn is_admin(command: &ApplicationCommandInteraction, ctx: &Context) ->
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
loop through all members of server
|
|
||||||
get a list of folks with mc accounts that are members
|
|
||||||
and a list that arent members
|
|
||||||
*/
|
|
||||||
pub async fn update_server(server_id: &str, db: &Pool<Sqlite>, g_id: &GuildId, config: &Config) {
|
|
||||||
let mut usernames = vec![];
|
|
||||||
for member in get_server_member_bulk(db, g_id).await {
|
|
||||||
if let Some(x) = member.minecraft {
|
|
||||||
usernames.push(x);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !usernames.is_empty() {
|
|
||||||
whitelist_update(&usernames, server_id, &config.discord_token_minecraft).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn post<T: Serialize>(url: &str, bearer: &str, data: &T) {
|
|
||||||
match surf::post(url)
|
|
||||||
.header("Authorization", bearer)
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.header("Accept", "Application/vnd.pterodactyl.v1+json")
|
|
||||||
.body_json(&data)
|
|
||||||
{
|
|
||||||
Ok(req) => {
|
|
||||||
req.await.ok();
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct ServerDetailsResSub {
|
|
||||||
pub identifier: String,
|
|
||||||
pub name: String,
|
|
||||||
pub description: String,
|
|
||||||
pub is_suspended: bool,
|
|
||||||
}
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
pub struct ServerDetailsRes {
|
|
||||||
pub attributes: ServerDetailsResSub,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get<T: Serialize + DeserializeOwned>(url: &str, bearer: &str) -> Option<T> {
|
|
||||||
match surf::get(url)
|
|
||||||
.header("Authorization", bearer)
|
|
||||||
.header("Content-Type", "application/json")
|
|
||||||
.header("Accept", "Application/vnd.pterodactyl.v1+json")
|
|
||||||
.recv_json()
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(res) => Some(res),
|
|
||||||
Err(e) => {
|
|
||||||
dbg!(e);
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
struct BodyCommand {
|
|
||||||
command: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
|
||||||
struct BodyDelete {
|
|
||||||
root: String,
|
|
||||||
files: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn whitelist_update(add: &Vec<String>, server: &str, token: &str) {
|
|
||||||
let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}");
|
|
||||||
let bearer = format!("Bearer {token}");
|
|
||||||
|
|
||||||
for name in add {
|
|
||||||
let data = BodyCommand {
|
|
||||||
command: format!("whitelist add {name}"),
|
|
||||||
};
|
|
||||||
post(&format!("{url_base}/command"), &bearer, &data).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn whitelist_wipe(server: &str, token: &str) {
|
|
||||||
let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}");
|
|
||||||
let bearer = format!("Bearer {token}");
|
|
||||||
|
|
||||||
// delete whitelist
|
|
||||||
let deletion = BodyDelete {
|
|
||||||
root: "/".to_string(),
|
|
||||||
files: vec!["whitelist.json".to_string()],
|
|
||||||
};
|
|
||||||
post(&format!("{url_base}/files/delete"), &bearer, &deletion).await;
|
|
||||||
|
|
||||||
// recreate teh file, passing in the type here so the compiler knows what type of vec it is
|
|
||||||
post::<Vec<&str>>(&format!("{url_base}/files/write?file=%2Fwhitelist.json"), &bearer, &vec![]).await;
|
|
||||||
|
|
||||||
// reload the whitelist
|
|
||||||
let data = BodyCommand {
|
|
||||||
command: "whitelist reload".to_string(),
|
|
||||||
};
|
|
||||||
post(&format!("{url_base}/command"), &bearer, &data).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn server_information(server: &str, token: &str) -> Option<ServerDetailsRes> {
|
|
||||||
let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}");
|
|
||||||
let bearer = format!("Bearer {token}");
|
|
||||||
get::<ServerDetailsRes>(&format!("{url_base}/"), &bearer).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_minecraft_config(db: &Pool<Sqlite>) -> Vec<Minecraft> {
|
|
||||||
sqlx::query_as::<_, Minecraft>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM minecraft
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.fetch_all(db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_minecraft_config_server(db: &Pool<Sqlite>, g_id: GuildId) -> Vec<Minecraft> {
|
|
||||||
sqlx::query_as::<_, Minecraft>(
|
|
||||||
r#"
|
|
||||||
SELECT *
|
|
||||||
FROM minecraft
|
|
||||||
WHERE server_discord = ?1
|
|
||||||
"#,
|
|
||||||
)
|
|
||||||
.bind(*g_id.as_u64() as i64)
|
|
||||||
.fetch_all(db)
|
|
||||||
.await
|
|
||||||
.unwrap_or_default()
|
|
||||||
}
|
|
||||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -13,7 +13,9 @@ use serenity::{
|
||||||
},
|
},
|
||||||
Client,
|
Client,
|
||||||
};
|
};
|
||||||
use skynet_discord_bot::{db_init, get_config, get_server_config, get_server_member, Config, DataBase};
|
use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_member, DataBase};
|
||||||
|
use skynet_discord_bot::common::set_roles::committee::update_committees;
|
||||||
|
use skynet_discord_bot::{get_config, Config};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
|
@ -29,7 +31,21 @@ impl EventHandler for Handler {
|
||||||
};
|
};
|
||||||
|
|
||||||
let db = db_lock.read().await;
|
let db = db_lock.read().await;
|
||||||
let config = match get_server_config(&db, &new_member.guild_id).await {
|
|
||||||
|
let config_lock = {
|
||||||
|
let data_read = ctx.data.read().await;
|
||||||
|
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
|
||||||
|
};
|
||||||
|
let config_global = config_lock.read().await;
|
||||||
|
|
||||||
|
// committee server takes priority
|
||||||
|
if new_member.guild_id.eq(&config_global.committee_server) {
|
||||||
|
let mut member = vec![new_member.clone()];
|
||||||
|
update_committees(&db, &ctx, &config_global, &mut member).await;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let config_server = match get_server_config(&db, &new_member.guild_id).await {
|
||||||
None => return,
|
None => return,
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
};
|
};
|
||||||
|
@ -37,14 +53,14 @@ impl EventHandler for Handler {
|
||||||
if get_server_member(&db, &new_member.guild_id, &new_member).await.is_ok() {
|
if get_server_member(&db, &new_member.guild_id, &new_member).await.is_ok() {
|
||||||
let mut roles = vec![];
|
let mut roles = vec![];
|
||||||
|
|
||||||
if let Some(role) = &config.role_past {
|
if let Some(role) = &config_server.role_past {
|
||||||
if !new_member.roles.contains(role) {
|
if !new_member.roles.contains(role) {
|
||||||
roles.push(role.to_owned());
|
roles.push(role.to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !new_member.roles.contains(&config.role_current) {
|
if !new_member.roles.contains(&config_server.role_current) {
|
||||||
roles.push(config.role_current.to_owned());
|
roles.push(config_server.role_current.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = new_member.add_roles(&ctx, &roles).await {
|
if let Err(e) = new_member.add_roles(&ctx, &roles).await {
|
||||||
|
@ -57,10 +73,10 @@ Welcome {} to the {} server!
|
||||||
Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use ``/link_wolves`` to get full access.
|
Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use ``/link_wolves`` to get full access.
|
||||||
"#,
|
"#,
|
||||||
new_member.display_name(),
|
new_member.display_name(),
|
||||||
&config.server_name,
|
&config_server.server_name,
|
||||||
&config.wolves_link,
|
&config_server.wolves_link,
|
||||||
&config.server,
|
&config_server.server,
|
||||||
&config.bot_channel_id
|
&config_server.bot_channel_id
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Err(err) = new_member.user.direct_message(&ctx, |m| m.content(&msg)).await {
|
if let Err(err) = new_member.user.direct_message(&ctx, |m| m.content(&msg)).await {
|
||||||
|
@ -83,9 +99,6 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
|
||||||
.create_application_command(|command| commands::minecraft::server::list::register(command))
|
.create_application_command(|command| commands::minecraft::server::list::register(command))
|
||||||
.create_application_command(|command| commands::minecraft::server::delete::register(command))
|
.create_application_command(|command| commands::minecraft::server::delete::register(command))
|
||||||
.create_application_command(|command| commands::minecraft::user::add::register(command))
|
.create_application_command(|command| commands::minecraft::user::add::register(command))
|
||||||
// for committee server, temp
|
|
||||||
.create_application_command(|command| commands::committee::link::register(command))
|
|
||||||
.create_application_command(|command| commands::committee::verify::register(command))
|
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
@ -112,9 +125,6 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
|
||||||
"minecraft_add" => commands::minecraft::server::add::run(&command, &ctx).await,
|
"minecraft_add" => commands::minecraft::server::add::run(&command, &ctx).await,
|
||||||
"minecraft_list" => commands::minecraft::server::list::run(&command, &ctx).await,
|
"minecraft_list" => commands::minecraft::server::list::run(&command, &ctx).await,
|
||||||
"minecraft_delete" => commands::minecraft::server::delete::run(&command, &ctx).await,
|
"minecraft_delete" => commands::minecraft::server::delete::run(&command, &ctx).await,
|
||||||
// for teh committee server, temporary
|
|
||||||
"link_committee" => commands::committee::link::run(&command, &ctx).await,
|
|
||||||
"verify_committee" => commands::committee::verify::run(&command, &ctx).await,
|
|
||||||
_ => "not implemented :(".to_string(),
|
_ => "not implemented :(".to_string(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue