Soo much bootstrapping

This commit is contained in:
Malte Tammena 2021-11-28 21:02:08 +01:00
parent 06ac88f6ff
commit 3455f323fc
19 changed files with 1265 additions and 127 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
/target
/result
/.env
state.yml

522
Cargo.lock generated
View file

@ -17,6 +17,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "async-channel"
version = "1.6.1"
@ -281,7 +290,7 @@ version = "2.33.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
dependencies = [
"ansi_term",
"ansi_term 0.11.0",
"atty",
"bitflags",
"strsim",
@ -310,6 +319,22 @@ dependencies = [
"cache-padded",
]
[[package]]
name = "core-foundation"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6888e10551bb93e424d8df1d07f1a8b4fceb0001a3a4b048bfc47554946f47b3"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "cpufeatures"
version = "0.2.1"
@ -321,9 +346,9 @@ dependencies = [
[[package]]
name = "crc32fast"
version = "1.2.1"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
checksum = "3825b1e8580894917dc4468cb634a1b4e9745fddc854edad72d9c04644c0319f"
dependencies = [
"cfg-if",
]
@ -363,6 +388,12 @@ version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dtoa"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0"
[[package]]
name = "encoding_rs"
version = "0.8.29"
@ -419,6 +450,21 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.0.1"
@ -431,12 +477,13 @@ dependencies = [
[[package]]
name = "futures"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12aa0eb539080d55c3f2d45a67c3b58b6b0773c1a3ca2dfec66d58c97fd66ca"
checksum = "8cd0210d8c325c245ff06fd95a3b13689a1a276ac8cfa8e8720cb840bfb84b9e"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
@ -445,9 +492,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5da6ba8c3bb3c165d3c7319fc1cc8304facf1fb8db99c5de877183c08a273888"
checksum = "7fc8cd39e3dbf865f7340dce6a2d401d24fd37c6fe6c4f0ee0de8bfca2252d27"
dependencies = [
"futures-core",
"futures-sink",
@ -455,15 +502,26 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d"
checksum = "629316e42fe7c2a0b9a65b47d159ceaa5453ab14e8f0a3c5eedbb8cd55b4a445"
[[package]]
name = "futures-executor"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b808bf53348a36cab739d7e04755909b9fcaaa69b7d7e588b37b6ec62704c97"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "522de2a0fe3e380f1bc577ba0474108faf3f6b18321dbf60b3b9c39a75073377"
checksum = "e481354db6b5c353246ccf6a728b0c5511d752c08da7260546fc0933869daa11"
[[package]]
name = "futures-lite"
@ -482,12 +540,10 @@ dependencies = [
[[package]]
name = "futures-macro"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18e4a4b95cea4b4ccbcf1c5675ca7c4ee4e9e75eb79944d07defde18068f79bb"
checksum = "a89f17b21645bc4ed773c69af9c9a0effd4a3f1a3876eadd453469f8854e7fdd"
dependencies = [
"autocfg",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
@ -495,23 +551,22 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36ea153c13024fe480590b3e3d4cad89a0cfacecc24577b68f86c6ced9c2bc11"
checksum = "996c6442437b62d21a32cd9906f9c41e7dc1e19a9579843fad948696769305af"
[[package]]
name = "futures-task"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d3d00f4eddb73e498a54394f228cd55853bdf059259e8e7bc6e69d408892e99"
checksum = "dabf1872aaab32c886832f2276d2f5399887e2bd613698a02359e4ea83f8de12"
[[package]]
name = "futures-util"
version = "0.3.17"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36568465210a3a6ee45e1f165136d68671471a501e632e9a98d96872222b5481"
checksum = "41d22213122356472061ac0f1ab2cee28d2bac8491410fd68c2af53d1cedb83e"
dependencies = [
"autocfg",
"futures-channel",
"futures-core",
"futures-io",
@ -521,8 +576,6 @@ dependencies = [
"memchr",
"pin-project-lite",
"pin-utils",
"proc-macro-hack",
"proc-macro-nested",
"slab",
]
@ -547,17 +600,34 @@ dependencies = [
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
dependencies = [
"cfg-if",
"libc",
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
name = "glados"
version = "0.1.0"
dependencies = [
"dotenv",
"influx_db_client",
"lazy_static",
"nom",
"rcon",
"rustbreak",
"serde",
"serenity",
"structopt",
"thiserror",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]
@ -646,9 +716,9 @@ checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
[[package]]
name = "httpdate"
version = "1.0.1"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
@ -689,6 +759,19 @@ dependencies = [
"webpki",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes 1.1.0",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "idna"
version = "0.2.3"
@ -710,6 +793,19 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "influx_db_client"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1a5abf7f7759f14075abcc0140d79a339729e50042ddf93fc0de9d11ddb1f"
dependencies = [
"bytes 1.1.0",
"futures",
"reqwest",
"serde",
"serde_json",
]
[[package]]
name = "input_buffer"
version = "0.3.1"
@ -766,9 +862,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.107"
version = "0.2.108"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119"
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "log"
@ -780,6 +882,15 @@ dependencies = [
"value-bag",
]
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata",
]
[[package]]
name = "matches"
version = "0.1.9"
@ -808,6 +919,12 @@ dependencies = [
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.4.4"
@ -840,6 +957,35 @@ dependencies = [
"winapi",
]
[[package]]
name = "native-tls"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nom"
version = "7.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d11e1ef389c76fe5b81bcaf2ea32cf88b62bc494e19f493d0b30e7a930109"
dependencies = [
"memchr",
"minimal-lexical",
"version_check",
]
[[package]]
name = "ntapi"
version = "0.3.6"
@ -890,6 +1036,39 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-sys",
]
[[package]]
name = "openssl-probe"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a"
[[package]]
name = "openssl-sys"
version = "0.9.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7df13d165e607909b363a4757a6f133f8a818a74e9d3a98d09c6128e15fa4c73"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "parking"
version = "2.0.0"
@ -934,6 +1113,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f"
[[package]]
name = "polling"
version = "2.2.0"
@ -977,18 +1162,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]]
name = "proc-macro-nested"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086"
[[package]]
name = "proc-macro2"
version = "1.0.32"
@ -1013,11 +1186,23 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"getrandom 0.1.16",
"libc",
"rand_chacha",
"rand_core",
"rand_hc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc 0.2.0",
]
[[package]]
name = "rand"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
dependencies = [
"libc",
"rand_chacha 0.3.1",
"rand_core 0.6.3",
"rand_hc 0.3.1",
]
[[package]]
@ -1027,7 +1212,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core",
"rand_core 0.5.1",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.3",
]
[[package]]
@ -1036,7 +1231,16 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom",
"getrandom 0.1.16",
]
[[package]]
name = "rand_core"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
dependencies = [
"getrandom 0.2.3",
]
[[package]]
@ -1045,20 +1249,71 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core",
"rand_core 0.5.1",
]
[[package]]
name = "rand_hc"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
dependencies = [
"rand_core 0.6.3",
]
[[package]]
name = "rcon"
version = "0.5.1"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465a6f903164a399084787547a026b83e7937bc576d8acdbd9e41ebf5de90a85"
checksum = "6b7fdd146f86bd90fa2d4cf83a28b45f058e90bcf11ed0cce134e757928771e6"
dependencies = [
"async-std",
"bytes 1.1.0",
"err-derive",
]
[[package]]
name = "redox_syscall"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
"winapi",
]
[[package]]
name = "reqwest"
version = "0.11.6"
@ -1074,12 +1329,14 @@ dependencies = [
"http-body",
"hyper",
"hyper-rustls",
"hyper-tls",
"ipnet",
"js-sys",
"lazy_static",
"log",
"mime",
"mime_guess",
"native-tls",
"percent-encoding",
"pin-project-lite",
"rustls",
@ -1087,6 +1344,7 @@ dependencies = [
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"url",
"wasm-bindgen",
@ -1111,6 +1369,18 @@ dependencies = [
"winapi",
]
[[package]]
name = "rustbreak"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "460d97902465327d69ecfe8cefdb5972c6f94d6127ac9e992acdb51458bebc27"
dependencies = [
"serde",
"serde_yaml",
"tempfile",
"thiserror",
]
[[package]]
name = "rustls"
version = "0.19.1"
@ -1136,6 +1406,16 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "schannel"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
dependencies = [
"lazy_static",
"winapi",
]
[[package]]
name = "sct"
version = "0.6.1"
@ -1146,6 +1426,29 @@ dependencies = [
"untrusted",
]
[[package]]
name = "security-framework"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525bc1abfda2e1998d152c45cf13e696f76d0a4972310b22fac1658b05df7c87"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9dd14d83160b528b7bfd66439110573efcfbe281b17fc2ca9f39f550d619c7e"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "serde"
version = "1.0.130"
@ -1168,9 +1471,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.70"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e277c495ac6cd1a01a58d0a0c574568b4d1ddf14f59965c6a58b8d96400b54f3"
checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19"
dependencies = [
"itoa",
"ryu",
@ -1189,6 +1492,18 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8c608a35705a5d3cdc9fbe403147647ff34b921f8e833e49306df898f9b20af"
dependencies = [
"dtoa",
"indexmap",
"serde",
"yaml-rust",
]
[[package]]
name = "serenity"
version = "0.10.9"
@ -1229,12 +1544,27 @@ dependencies = [
"opaque-debug",
]
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "slab"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]]
name = "smallvec"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
name = "socket2"
version = "0.4.2"
@ -1310,6 +1640,20 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "tempfile"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
dependencies = [
"cfg-if",
"libc",
"rand 0.8.4",
"redox_syscall",
"remove_dir_all",
"winapi",
]
[[package]]
name = "textwrap"
version = "0.11.0"
@ -1340,13 +1684,21 @@ dependencies = [
]
[[package]]
name = "time"
version = "0.1.44"
name = "thread_local"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
@ -1392,6 +1744,16 @@ dependencies = [
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.22.0"
@ -1456,6 +1818,35 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "tracing-log"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7507ec620f809cdf07cccb5bc57b13069a88031b795efd4079b1c71b66c1613d"
dependencies = [
"ansi_term 0.12.1",
"lazy_static",
"matchers",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
name = "try-lock"
version = "0.2.3"
@ -1475,7 +1866,7 @@ dependencies = [
"httparse",
"input_buffer",
"log",
"rand",
"rand 0.7.3",
"sha-1",
"url",
"utf-8",
@ -1575,6 +1966,12 @@ dependencies = [
"version_check",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec_map"
version = "0.8.2"
@ -1611,9 +2008,9 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
version = "0.10.2+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-bindgen"
@ -1758,3 +2155,12 @@ checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
dependencies = [
"winapi",
]
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]

View file

@ -12,9 +12,15 @@ toolchain = "nightly"
dotenv = "0.15.0"
lazy_static = "1.4.0"
rcon = "0.5.1"
rustbreak = { version = "2.0.0", features = [ "yaml_enc" ] }
serde = { version = "1.0.130", features = [ "derive" ] }
serenity = "0.10.9"
structopt = "0.3.25"
thiserror = "1.0.30"
tracing = "0.1.29"
tracing-subscriber = { version = "0.3.2", features = [ "env-filter" ] }
influx_db_client = "0.5.0"
nom = "7.1.0"
[dependencies.tokio]
version = "1.14.0"

View file

@ -22,11 +22,11 @@
"rustOverlay": "rustOverlay"
},
"locked": {
"lastModified": 1637129418,
"narHash": "sha256-bO6rLgIiqK6pdeF2ewKyD6c+hNAcBEfXDqiTRaWzNmo=",
"lastModified": 1637302206,
"narHash": "sha256-X82LW/R35vCxNSk9jcddZFbjO6ZMjsq+KhIGC/GMkJg=",
"owner": "yusdacra",
"repo": "nix-cargo-integration",
"rev": "1faede2be6c28a68a00b3479b77c849720324511",
"rev": "a25206065a3a19d3dbcb2192d9bd273eea5cd919",
"type": "github"
},
"original": {
@ -59,11 +59,11 @@
"rustOverlay": {
"flake": false,
"locked": {
"lastModified": 1637115307,
"narHash": "sha256-G+RKZeE1yLrnq+ExHF+HnSJsT+QJWebGhssgZHz3B00=",
"lastModified": 1637288133,
"narHash": "sha256-x5XWEK333KEhy2WL3TafE1vSa8/A1sGdbirTIV2bmSc=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "8a2e5fa870df3d34667d28fb3383d19516d182e4",
"rev": "ccc467eff80b2fbb8000cf425e999ef14fbe200c",
"type": "github"
},
"original": {

View file

@ -1,7 +1,8 @@
use std::collections::HashSet;
use std::{collections::HashSet, path::PathBuf};
use influx_db_client::reqwest::Url;
use lazy_static::lazy_static;
use serenity::model::id::UserId;
use serenity::model::id::{ChannelId, GuildId, UserId};
use structopt::StructOpt;
lazy_static! {
@ -14,17 +15,29 @@ pub struct Args {
short = "t",
value_name = "TOKEN",
validator = validate_token,
env = "DISCORD_TOKEN")]
env = "DISCORD_TOKEN",
hide_env_values = true)]
pub discord_token: String,
#[structopt(long, value_name = "URL", env = "RCON_ADDRESS")]
pub rcon_address: String,
#[structopt(long, value_name = "PASSWORD", env = "RCON_PASSWORD")]
#[structopt(
long,
value_name = "PASSWORD",
env = "RCON_PASSWORD",
hide_env_values = true
)]
pub rcon_password: String,
#[structopt(long, short, value_name = "ID", env = "DISCORD_OWNERS")]
pub owners: Vec<UserId>,
#[structopt(
long,
short,
value_name = "ID",
env = "DISCORD_OWNERS",
use_delimiter = true
)]
pub owner: Vec<UserId>,
#[structopt(
long,
@ -34,6 +47,36 @@ pub struct Args {
default_value = "~"
)]
pub prefix: String,
#[structopt(
long = "db",
short,
value_name = "PATH",
env = "DB_PATH",
default_value = "state.yml"
)]
pub database_path: PathBuf,
#[structopt(long, short, value_name = "ID", env = "GUILD_ID")]
pub guild_id: u64,
#[structopt(long, value_name = "ID", env = "GUILD_ADMIN_CHANNEL")]
pub admin_channel: u64,
#[structopt(long, value_name = "ID", env = "GUILD_INFO_CHANNEL")]
pub info_channel: u64,
#[structopt(long, value_name = "URL", env = "INFLUX_HOST")]
pub influx_host: Url,
#[structopt(long, value_name = "DB", env = "INFLUX_DB")]
pub influx_db: String,
#[structopt(long, value_name = "USER", env = "INFLUX_USER")]
pub influx_user: String,
#[structopt(long, value_name = "PW", env = "INFLUX_PW", hide_env_values = true)]
pub influx_password: String,
}
fn validate_token(raw: String) -> std::result::Result<(), String> {
@ -42,9 +85,21 @@ fn validate_token(raw: String) -> std::result::Result<(), String> {
impl Args {
pub fn owners(&self) -> HashSet<UserId> {
if self.owners.is_empty() {
eprintln!("You should probably specify at least one `--owner`");
if self.owner.is_empty() {
tracing::warn!("You should probably specify at least one `--owner`");
}
self.owners.iter().cloned().collect()
self.owner.iter().cloned().collect()
}
pub fn guild_id(&self) -> GuildId {
self.guild_id.into()
}
pub fn admin_channel(&self) -> ChannelId {
self.admin_channel.into()
}
pub fn info_channel(&self) -> ChannelId {
self.info_channel.into()
}
}

16
src/data.rs Normal file
View file

@ -0,0 +1,16 @@
use rustbreak::deser::Yaml;
use serenity::prelude::TypeMapKey;
use crate::{rcon::RconHandle, state::State};
pub struct StateKey;
impl TypeMapKey for StateKey {
type Value = rustbreak::FileDatabase<State, Yaml>;
}
pub struct Rcon;
impl TypeMapKey for Rcon {
type Value = RconHandle;
}

View file

@ -1,33 +1,55 @@
use serenity::{
framework::{standard::macros::group, StandardFramework},
Client,
};
//! # Workflows
//!
//! - `~ping`
//! - Pong!
//!
//! - `~invite Random#1234`
//! - Invite someone into the guild
//!
//! - Member_add
//! - Notify guild about new member
//!
//! - Member_leave
//! - Notify guild about leave
use lazy_static::lazy_static;
use serenity::{client::bridge::gateway::GatewayIntents, Client};
use crate::{
conf::ARGS,
data,
error::{Error, Result},
influx::{InfluxDb, InfluxKey},
rcon::RconHandle,
state::State,
};
pub struct Api {
mod framework;
mod handler;
mod util;
use self::handler::Handler;
lazy_static! {
static ref INTENTS: GatewayIntents = {
use GatewayIntents as GI;
GI::DIRECT_MESSAGES | GI::DIRECT_MESSAGE_REACTIONS | GI::GUILD_MEMBERS | GI::GUILD_MESSAGES
};
}
pub struct Bot {
client: Client,
}
#[group]
#[owners_only]
struct Owner;
impl Api {
impl Bot {
pub async fn init() -> Result<Self> {
let framework = StandardFramework::new()
.configure(|c| {
c.with_whitespace(true)
.owners(ARGS.owners())
.prefix(&ARGS.prefix)
.delimiters(vec![", ", ","])
})
.group(&OWNER_GROUP);
let client = Client::builder(&ARGS.discord_token)
.framework(framework)
.framework(framework::init())
.intents(*INTENTS)
.event_handler(Handler::default())
.type_map_insert::<data::Rcon>(RconHandle::init().await)
.type_map_insert::<InfluxKey>(InfluxDb::init())
.type_map_insert::<data::StateKey>(State::load().await?)
.await
.map_err(Error::CreatingDiscordClient)?;
Ok(Self { client })

67
src/discord/framework.rs Normal file
View file

@ -0,0 +1,67 @@
use std::collections::HashSet;
use serenity::{
client::Context,
framework::{
standard::{
help_commands,
macros::{command, group, help},
Args, CommandGroup, CommandResult, HelpOptions,
},
StandardFramework,
},
model::{channel::Message, id::UserId},
};
use crate::conf::ARGS;
pub fn init() -> StandardFramework {
StandardFramework::new()
.configure(|c| {
c.with_whitespace(true)
.owners(ARGS.owners())
.prefix(&ARGS.prefix)
.delimiters(vec![", ", ","])
})
.help(&BOT_HELP)
.group(&OWNER_GROUP)
.group(&GENERAL_GROUP)
}
#[group]
#[owners_only]
#[commands(ping, invite)]
struct Owner;
#[group]
struct General;
#[command]
#[description = "Ping the bot to see if it's online"]
async fn ping(ctx: &Context, msg: &Message) -> CommandResult {
msg.reply(&ctx.http, "Pong!").await?;
Ok(())
}
#[command]
#[description = "Invite a new member to the guild"]
async fn invite(ctx: &Context, msg: &Message) -> CommandResult {
msg.reply(&ctx.http, "TBD").await?;
Ok(())
}
#[help]
#[max_levenshtein_distance(3)]
#[lacking_permissions = "Hide"]
#[lacking_role = "Nothing"]
async fn bot_help(
context: &Context,
msg: &Message,
args: Args,
help_options: &'static HelpOptions,
groups: &[&'static CommandGroup],
owners: HashSet<UserId>,
) -> CommandResult {
let _ = help_commands::with_embeds(context, msg, args, help_options, groups, owners).await;
Ok(())
}

57
src/discord/handler.rs Normal file
View file

@ -0,0 +1,57 @@
use serenity::{
client::{Context, EventHandler},
model::{
guild::Member,
id::GuildId,
prelude::{Ready, User},
},
};
use std::sync::atomic::{AtomicBool, Ordering};
use crate::{conf::ARGS, error::ResultExt, tracking::track_server_status};
use super::util::{initial_state_sanitization, workflow_member_add, workflow_member_removal};
#[derive(Debug, Default)]
pub struct Handler {
setup_done: AtomicBool,
}
#[serenity::async_trait]
impl EventHandler for Handler {
async fn ready(&self, ctx: Context, _data_about_bot: Ready) {
// Only do this once!
if !self.setup_done.load(Ordering::Relaxed) {
let ctx_clone = ctx.clone();
initial_state_sanitization(&ctx_clone)
.await
.log_warn("sanitizing state at startup");
tokio::spawn(track_server_status(ctx_clone));
self.setup_done.swap(true, Ordering::Relaxed);
}
}
async fn guild_member_addition(&self, ctx: Context, guild_id: GuildId, new_member: Member) {
if guild_id != ARGS.guild_id {
return;
}
workflow_member_add(&ctx, new_member).await;
}
async fn guild_member_removal(
&self,
ctx: Context,
guild_id: GuildId,
user: User,
_member_data: Option<Member>,
) {
if guild_id != ARGS.guild_id {
return;
}
workflow_member_removal(&ctx, user).await;
}
}

107
src/discord/util.rs Normal file
View file

@ -0,0 +1,107 @@
use serenity::{
client::Context,
model::{guild::Member, prelude::User},
};
use core::fmt;
use std::collections::HashSet;
use crate::{
conf::ARGS,
error::{Result, ResultExt},
state::{GuildMember, State},
};
pub async fn initial_state_sanitization(ctx: &Context) -> Result {
catch_up_on_guild_members(ctx).await?;
Ok(())
}
pub async fn workflow_member_add(ctx: &Context, new_member: Member) {
tracing::info!("Adding new member {:?}", new_member.user.name);
// Keep track of the new member
State::write(ctx, |state| {
state.guild_members.insert(new_member.user.id.into())
})
.await
.log_warn("adding new member to guild list");
// Notify about new arrival
admin_channel_say(ctx, format!("{} joined the guild!", new_member.user))
.await
.log_warn("notifying about new guild member");
// Greet the new member with a DM
welcome_new_member(ctx, &new_member)
.await
.log_warn("welcoming new member");
}
pub async fn workflow_member_removal(ctx: &Context, user: User) {
let old_member: Option<GuildMember> =
State::write(ctx, |state| state.guild_members.take(&user.id))
.await
.log_warn("removing ex-guild-member")
.flatten();
if old_member.is_some() {
admin_channel_say(ctx, format!("{} left the server!", user))
.await
.log_warn("notifying guild about leaving member");
}
}
async fn welcome_new_member(ctx: &Context, member: &Member) -> Result {
let channel = member.user.create_dm_channel(ctx.http.clone()).await?;
channel.send_message(ctx.http.clone(), |m| {
m.content(format!(r#"Hey {},
Welcome to the CCQCraft Server! Most information can be found in the {info_channel} channel! If you have any questions message either megamanmalte or Gim. Not me, I'm just a bot :)"#,
member.user.name,
info_channel=ARGS.info_channel()))
}).await?;
Ok(())
}
async fn admin_channel_say(ctx: &Context, what: impl fmt::Display) -> Result {
ARGS.admin_channel().say(ctx.http.clone(), what).await?;
Ok(())
}
async fn catch_up_on_guild_members(ctx: &Context) -> Result {
let actual_members: HashSet<_> = ARGS
.guild_id()
.members(ctx.http.clone(), None, None)
.await?
.into_iter()
.map(|member| member.user.id)
.collect();
let known_members: HashSet<_> = State::read(ctx, |state| {
state.guild_members.iter().map(|member| member.id).collect()
})
.await?;
for old in known_members.difference(&actual_members) {
let old_user = match old
.to_user(ctx.http.clone())
.await
.log_warn("retrieving user based on id")
{
Some(member) => member,
None => continue,
};
workflow_member_removal(ctx, old_user).await;
}
for new in actual_members.difference(&known_members) {
let new_member = match ARGS
.guild_id()
.member(ctx.http.clone(), new)
.await
.log_warn("retrieving member based on id")
{
Some(member) => member,
None => continue,
};
workflow_member_add(ctx, new_member).await;
}
Ok(())
}

View file

@ -1,11 +1,11 @@
use core::fmt;
use thiserror::Error;
pub type Result<T = ()> = ::std::result::Result<T, Error>;
#[derive(Debug, Error)]
pub enum Error {
#[error("Configuration key {_0:?} is missing. Try setting it through args or the environment")]
MissingConfiguration(String),
#[error("Failed to initialize RCON: {_0}")]
InitializingRcon(#[source] rcon::Error),
#[error("Failed to execute RCON command {_0:?}: {_1}")]
@ -14,4 +14,53 @@ pub enum Error {
CreatingDiscordClient(#[source] serenity::Error),
#[error("Error while listening for Discord events: {_0}")]
Serenity(#[from] serenity::Error),
#[error("Error while handling database: {_0}")]
Rustbreak(#[from] rustbreak::error::RustbreakError),
#[error("Error while talking to Influx db: {_0}")]
Influx(#[from] influx_db_client::Error),
#[error("Parsing results of `tps` command: {_0}")]
RconTpsCmd(#[source] nom::Err<nom::error::Error<String>>),
#[error("Parsing results of `list` command: {_0}")]
RconListCmd(#[source] nom::Err<nom::error::Error<String>>),
}
pub trait ResultExt<T> {
fn log_info(self, when: &str) -> Option<T>;
fn log_warn(self, when: &str) -> Option<T>;
fn log_error(self, when: &str) -> Option<T>;
}
impl<T, E> ResultExt<T> for std::result::Result<T, E>
where
E: fmt::Display,
{
fn log_info(self, when: &str) -> Option<T> {
match self {
Ok(val) => Some(val),
Err(why) => {
tracing::info!("Error occured while {}: {}", when, why);
None
}
}
}
fn log_warn(self, when: &str) -> Option<T> {
match self {
Ok(val) => Some(val),
Err(why) => {
tracing::warn!("Error occured while {}: {}", when, why);
None
}
}
}
fn log_error(self, when: &str) -> Option<T> {
match self {
Ok(val) => Some(val),
Err(why) => {
tracing::error!("Error occured while {}: {}", when, why);
None
}
}
}
}

44
src/influx.rs Normal file
View file

@ -0,0 +1,44 @@
use influx_db_client::{Client, Point};
use serenity::{client::Context, prelude::TypeMapKey};
use crate::{conf::ARGS, error::Result};
#[derive(Debug)]
pub struct InfluxDb {
inner: Client,
}
pub struct InfluxKey;
impl TypeMapKey for InfluxKey {
type Value = InfluxDb;
}
impl InfluxDb {
pub fn init() -> Self {
let inner = Client::new(ARGS.influx_host.clone(), &ARGS.influx_db)
.set_authentication(&ARGS.influx_user, &ARGS.influx_password);
Self { inner }
}
//pub async fn write_point(point: Point, ctx: &Context) -> Result {
// let client = ctx.data.read().await;
// let client = client
// .get::<InfluxKey>()
// .expect("BUG: No Influx client present");
// client.inner.write_point(point, None, None).await?;
// Ok(())
//}
pub async fn write_points<T>(points: T, ctx: &Context) -> Result
where
T: Iterator<Item = Point>,
{
let client = ctx.data.read().await;
let client = client
.get::<InfluxKey>()
.expect("BUG: No Influx client present");
client.inner.write_points(points, None, None).await?;
Ok(())
}
}

View file

@ -3,13 +3,16 @@
//! Helper tool for my minecraft instance.
mod conf;
mod data;
mod discord;
mod error;
mod influx;
mod rcon;
mod state;
mod tracking;
use discord::Bot;
use error::Result;
use state::State;
#[tokio::main(flavor = "current_thread")]
async fn main() {
@ -21,7 +24,7 @@ async fn main() {
async fn real_main() -> Result {
dotenv::dotenv().ok();
tracing_subscriber::fmt::init();
let state = State::init().await?;
state.run_bot().await
Bot::init().await?.start().await
}

View file

@ -1,33 +1,69 @@
use std::time::Duration;
use serenity::client::Context;
use crate::{
conf::ARGS,
error::{Error, Result},
data,
error::{Error, Result, ResultExt},
};
pub struct Rcon {
inner: ::rcon::Connection,
pub struct RconHandle {
inner: Option<::rcon::Connection>,
}
impl Rcon {
pub async fn init() -> Result<Self> {
pub trait RconCommand {
type Output;
fn as_string(&self) -> String;
fn parse(&self, raw: String) -> Result<Self::Output>;
}
impl RconHandle {
pub async fn init() -> Self {
let mut handle = Self { inner: None };
handle.connect().await;
handle
}
pub async fn cmd<C>(cmd: &C, ctx: &Context) -> Result<C::Output>
where
C: RconCommand,
{
let mut rcon = ctx.data.write().await;
let rcon = rcon.get_mut::<data::Rcon>().expect("BUG: No rcon client");
rcon.reconnect_until_success().await;
let cmd_str = cmd.as_string();
let raw_output = rcon
.inner
.as_mut()
// We must be connected here
.unwrap()
.cmd(&cmd_str)
.await
.map_err(|why| Error::RconCommand(cmd_str, why))?;
cmd.parse(raw_output)
}
async fn reconnect_until_success(&mut self) {
let mut wait = Duration::from_secs(1);
if self.inner.is_none() {
self.connect().await;
}
while self.inner.is_none() {
tokio::time::sleep(wait).await;
self.connect().await;
wait *= 2;
}
}
async fn connect(&mut self) {
let addr = &ARGS.rcon_address;
let password = &ARGS.rcon_password;
let inner = rcon::Connection::builder()
self.inner = rcon::Connection::builder()
.enable_minecraft_quirks(true)
.connect(addr, &password)
.connect(addr, password)
.await
.map_err(Error::InitializingRcon)?;
Ok(Self { inner })
}
pub async fn cmd(&mut self, cmd: &str) -> Result<String> {
self.inner
.cmd(cmd)
.await
.map_err(|why| Error::RconCommand(cmd.into(), why))
}
pub async fn greet(&mut self) -> Result {
let _ = self.cmd("say GLaDOS is now online").await?;
Ok(())
.map_err(Error::InitializingRcon)
.log_warn("initializing rcon connection");
}
}

View file

@ -1,18 +1,95 @@
use crate::{discord::Api, error::Result, rcon::Rcon};
use rustbreak::{deser::Yaml, FileDatabase};
use serde::{Deserialize, Serialize};
use serenity::{client::Context, model::id::UserId};
use std::{borrow::Borrow, collections::HashSet, hash::Hash};
use crate::{
conf::ARGS,
data,
error::{Error, Result, ResultExt},
};
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct State {
pub rcon: Rcon,
pub api: Api,
/// Keeps track of the current guild members
pub guild_members: HashSet<GuildMember>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GuildMember {
/// Discord ID of the member
pub id: UserId,
}
impl State {
pub async fn init() -> Result<Self> {
let rcon = Rcon::init().await?;
let api = Api::init().await?;
Ok(State { rcon, api })
pub async fn load() -> Result<FileDatabase<Self, Yaml>> {
let db = FileDatabase::load_from_path_or_default(&ARGS.database_path)?;
Ok(db)
}
pub async fn run_bot(self) -> Result {
self.api.start().await
pub async fn save(ctx: &Context) -> Result {
ctx.data
.read()
.await
.get::<data::StateKey>()
.expect("BUG: Missing state in TypeMap")
.save()?;
Ok(())
}
pub async fn read<F, R>(ctx: &Context, f: F) -> Result<R>
where
F: FnOnce(&State) -> R,
{
ctx.data
.read()
.await
.get::<data::StateKey>()
.expect("BUG: Missing state in TypeMap")
.read(f)
.map_err(Error::from)
}
pub async fn write<F, R>(ctx: &Context, f: F) -> Result<R>
where
F: FnOnce(&mut State) -> R,
{
let res = ctx
.data
.read()
.await
.get::<data::StateKey>()
.expect("BUG: Missing state in TypeMap")
.write(f)
.map_err(Error::from);
State::save(ctx).await.log_warn("saving state data to disk");
res
}
}
impl From<UserId> for GuildMember {
fn from(id: UserId) -> Self {
Self { id }
}
}
impl Hash for GuildMember {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl PartialEq for GuildMember {
fn eq(&self, other: &Self) -> bool {
self.id.eq(&other.id)
}
}
impl Eq for GuildMember {}
impl Borrow<UserId> for GuildMember {
fn borrow(&self) -> &UserId {
&self.id
}
}

26
src/tracking.rs Normal file
View file

@ -0,0 +1,26 @@
use serenity::client::Context;
use std::time::Duration;
mod status;
use crate::{error::ResultExt, tracking::status::Status};
const TIME_BETWEEN_TRACKS: Duration = Duration::from_secs(60);
pub async fn track_server_status(ctx: Context) {
loop {
tracing::info!("Collecting status information");
// Fetch status via rcon and if successful, push it to the influx db
if let Some(status) = Status::get_from_rcon(&ctx)
.await
.log_warn("fetching status via rcon")
{
status
.send_to_influx_db(&ctx)
.await
.log_warn("sending status to influx endpoint");
}
tokio::time::sleep(TIME_BETWEEN_TRACKS).await;
}
}

40
src/tracking/status.rs Normal file
View file

@ -0,0 +1,40 @@
use influx_db_client::Point;
use serenity::client::Context;
mod player_info;
mod tps;
use crate::{error::Result, influx::InfluxDb, rcon::RconHandle};
use self::{
player_info::{ListCmd, PlayerInfo},
tps::{Tps, TpsCmd},
};
pub struct Status {
pub tps: Tps,
pub player_info: PlayerInfo,
}
impl Status {
pub async fn get_from_rcon(ctx: &Context) -> Result<Self> {
let tps = RconHandle::cmd(&TpsCmd, ctx).await?;
let player_info = RconHandle::cmd(&ListCmd, ctx).await?;
Ok(Status { tps, player_info })
}
pub async fn send_to_influx_db(&self, ctx: &Context) -> Result {
let mut points = vec![];
let status = Point::new("status")
.add_field("tps", self.tps.min1)
// Safe, since we started with u32
.add_field("player_count", self.player_info.amount_online as i64);
points.push(status);
for player in &self.player_info.players {
let point = Point::new("activity").add_tag("user_id", player.uuid.clone());
points.push(point);
}
InfluxDb::write_points(points.into_iter(), ctx).await
}
}

View file

@ -0,0 +1,68 @@
use nom::{
bytes::complete::{is_not, tag},
character::complete::{self, char},
combinator::{all_consuming, map},
multi::separated_list0,
sequence::{delimited, tuple},
IResult,
};
use crate::{
error::{Error, Result},
rcon::RconCommand,
};
pub struct ListCmd;
pub struct PlayerInfo {
pub amount_online: u32,
pub players: Vec<Player>,
}
pub struct Player {
pub uuid: String,
}
impl RconCommand for ListCmd {
type Output = PlayerInfo;
fn as_string(&self) -> String {
"minecraft:list uuids".into()
}
/// Expected:
/// ```
/// There are 6 of a max of 20 players online: SomeName (00000000-0000-0000-0000-000000000000), ...
/// ```
fn parse(&self, raw: String) -> Result<Self::Output> {
parse_list_result(&raw)
.map(|(_, info)| info)
.map_err(|why| Error::RconListCmd(why.to_owned()))
}
}
fn parse_list_result(inp: &str) -> IResult<&str, PlayerInfo> {
all_consuming(map(
tuple((
tag("There are "),
complete::u32,
tag(" of a max of "),
complete::u32,
tag(" players online: "),
separated_list0(tag(", "), parse_player),
)),
|(_, num, _, _max, _, players)| PlayerInfo {
amount_online: num,
players,
},
))(inp)
}
fn parse_player(inp: &str) -> IResult<&str, Player> {
map(
tuple((is_not(" "), delimited(char('('), is_not(")"), char(')')))),
|(_name, uuid): (&str, &str)| Player {
uuid: uuid.to_owned(),
},
)(inp)
}

View file

@ -0,0 +1,58 @@
use nom::{
bytes::complete::tag,
combinator::{all_consuming, map, opt},
number::complete::double,
sequence::{preceded, tuple},
IResult,
};
use crate::{
error::{Error, Result},
rcon::RconCommand,
};
pub struct TpsCmd;
pub struct Tps {
pub min1: f64,
pub min5: f64,
pub min15: f64,
}
impl RconCommand for TpsCmd {
type Output = Tps;
fn as_string(&self) -> String {
"spigot:tps".into()
}
/// Expected:
/// ```
/// TPS from last 1m, 5m, 15m: 20.0, 20.0, 20.0
/// ```
fn parse(&self, raw: String) -> Result<Self::Output> {
parse_tps_result(&raw)
.map(|(_, tps)| tps)
.map_err(|why| Error::RconTpsCmd(why.to_owned()))
}
}
fn parse_tps_result(inp: &str) -> IResult<&str, Tps> {
let start = tag("§6TPS from last 1m, 5m, 15m: §a");
all_consuming(map(
tuple((
start,
tps_value,
tag(", §a"),
tps_value,
tag(", §a"),
tps_value,
tag("\n"),
)),
|(_, min1, _, min5, _, min15, _)| Tps { min1, min5, min15 },
))(inp)
}
fn tps_value(inp: &str) -> IResult<&str, f64> {
preceded(opt(tag("*")), double)(inp)
}