Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
No results found
Show changes
Commits on Source (69)
*.db filter=lfs diff=lfs merge=lfs -text
/target/
Cargo.lock
**/*.rs.bk
src/discord_token
ucc-bot.log
# Packages required to build
On ubuntu (such as using ubuntu with WSL) you will need these packages to build:
* libsqlite3-dev
* pkg-config
* libssl-dev
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "adler"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
[[package]]
name = "aes"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
dependencies = [
"aes-soft",
"aesni",
"cipher",
]
[[package]]
name = "aes-soft"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072"
dependencies = [
"cipher",
"opaque-debug",
]
[[package]]
name = "aesni"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce"
dependencies = [
"cipher",
"opaque-debug",
]
[[package]]
name = "aho-corasick"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
dependencies = [
"memchr",
]
[[package]]
name = "async-trait"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "async-tungstenite"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7cc5408453d37e2b1c6f01d8078af1da58b6cfa6a80fa2ede3bd2b9a6ada9c4"
dependencies = [
"futures-io",
"futures-util",
"log",
"pin-project",
"tokio",
"tokio-rustls",
"tungstenite",
"webpki-roots 0.20.0",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "base64"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
[[package]]
name = "base64"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array",
]
[[package]]
name = "block-modes"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0"
dependencies = [
"block-padding",
"cipher",
]
[[package]]
name = "block-padding"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]]
name = "bumpalo"
version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
[[package]]
name = "byteorder"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
[[package]]
name = "bytes"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
[[package]]
name = "bytes"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
[[package]]
name = "cc"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"serde",
"time",
"winapi",
]
[[package]]
name = "cipher"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801"
dependencies = [
"generic-array",
]
[[package]]
name = "const_fn"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
[[package]]
name = "core-foundation"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]]
name = "cpuid-bool"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
[[package]]
name = "crc32fast"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
dependencies = [
"cfg-if",
"const_fn",
"crossbeam-utils",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
dependencies = [
"autocfg",
"cfg-if",
"lazy_static",
]
[[package]]
name = "diesel"
version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2de9deab977a153492a1468d1b1c0662c1cf39e5ea87d0c060ecd59ef18d8c"
dependencies = [
"byteorder",
"diesel_derives",
"libsqlite3-sys",
]
[[package]]
name = "diesel_derives"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array",
]
[[package]]
name = "dtoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88d7ed2934d741c6b37e33e3832298e8850b53fd2d2bea03873375596c7cea4e"
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "encoding_rs"
version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065"
dependencies = [
"cfg-if",
]
[[package]]
name = "flate2"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
dependencies = [
"cfg-if",
"crc32fast",
"libc",
"miniz_oxide",
]
[[package]]
name = "fnv"
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.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00"
dependencies = [
"matches",
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65"
[[package]]
name = "futures-executor"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500"
[[package]]
name = "futures-macro"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd"
dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6"
[[package]]
name = "futures-task"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86"
dependencies = [
"once_cell",
]
[[package]]
name = "futures-util"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"proc-macro-hack",
"proc-macro-nested",
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
dependencies = [
"cfg-if",
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
]
[[package]]
name = "guard"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1f3d64ec55b293a2ddfee276c6d7733616925b55dd337ddc0dc83aba96fbbb1"
[[package]]
name = "h2"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b67e66362108efccd8ac053abafc8b7a8d86a37e6e48fc4f6f7485eb5e9e6a5"
dependencies = [
"bytes 1.0.1",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
"tracing-futures",
]
[[package]]
name = "hashbrown"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]]
name = "hermit-abi"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
dependencies = [
"libc",
]
[[package]]
name = "http"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747"
dependencies = [
"bytes 1.0.1",
"fnv",
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2861bd27ee074e5ee891e8b539837a9430012e249d7f0ca2d795650f579c1994"
dependencies = [
"bytes 1.0.1",
"http",
]
[[package]]
name = "httparse"
version = "1.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691"
[[package]]
name = "httpdate"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
[[package]]
name = "hyper"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8e946c2b1349055e0b72ae281b238baf1a3ea7307c7e9f9d64673bdd9c26ac7"
dependencies = [
"bytes 1.0.1",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64"
dependencies = [
"futures-util",
"hyper",
"log",
"rustls",
"tokio",
"tokio-rustls",
"webpki",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes 1.0.1",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "ical"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a9f7215ad0d77e69644570dee000c7678a47ba7441062c1b5f918adde0d73cf"
dependencies = [
"thiserror",
]
[[package]]
name = "idna"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21"
dependencies = [
"matches",
"unicode-bidi",
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b"
dependencies = [
"autocfg",
"hashbrown",
"serde",
]
[[package]]
name = "input_buffer"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754"
dependencies = [
"bytes 0.5.6",
]
[[package]]
name = "instant"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
dependencies = [
"cfg-if",
]
[[package]]
name = "ipnet"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135"
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "js-sys"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cfb73131c35423a367daf8cbd24100af0d077668c8c2943f0e7dd775fef0f65"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lber"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a99b520993b21a6faab32643cf4726573dc18ca4cf2d48cbeb24d248c86c930"
dependencies = [
"byteorder",
"bytes 1.0.1",
"nom",
]
[[package]]
name = "ldap3"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a26304b072ca0d9c809841ca0746c661d62ac09a77cab4781a373aa4f0ad4b7"
dependencies = [
"async-trait",
"bytes 1.0.1",
"futures",
"futures-util",
"lazy_static",
"lber",
"log",
"maplit",
"native-tls",
"nom",
"percent-encoding",
"thiserror",
"tokio",
"tokio-native-tls",
"tokio-stream",
"tokio-util",
"url",
]
[[package]]
name = "libc"
version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
[[package]]
name = "libsqlite3-sys"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e704a02bcaecd4a08b93a23f6be59d0bd79cd161e0963e9499165a0a35df7bd"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]]
name = "linked-hash-map"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "lock_api"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "matches"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
[[package]]
name = "memchr"
version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "memoffset"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
dependencies = [
"autocfg",
]
[[package]]
name = "mime"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "miniz_oxide"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
dependencies = [
"adler",
"autocfg",
]
[[package]]
name = "mio"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc250d6848c90d719ea2ce34546fb5df7af1d3fd189d10bf7bad80bfcebecd95"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]]
name = "miow"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897"
dependencies = [
"socket2",
"winapi",
]
[[package]]
name = "native-tls"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "nom"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf51a729ecf40266a2368ad335a5fdde43471f545a967109cd62146ecf8b66ff"
[[package]]
name = "ntapi"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
dependencies = [
"winapi",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038d43985d1ddca7a9900630d8cd031b56e4794eecc2e9ea39dd17aa04399a70"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"lazy_static",
"libc",
"openssl-sys",
]
[[package]]
name = "openssl-probe"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
[[package]]
name = "openssl-sys"
version = "0.9.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "921fc71883267538946025deffb622905ecad223c28efbfdef9bb59a0175f3e6"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "parking_lot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pin-project"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96fa8ebb90271c4477f144354485b8068bd8f6b78b428b01ba892ca26caf0b63"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758669ae3558c6f74bd2a18b41f7ac0b5a195aea6639d6a9b5e5d1ad5ba24c0b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pin-project-lite"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "ppv-lite86"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[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.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom 0.1.16",
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc 0.2.0",
]
[[package]]
name = "rand"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
dependencies = [
"libc",
"rand_chacha 0.3.0",
"rand_core 0.6.2",
"rand_hc 0.3.0",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
]
[[package]]
name = "rand_chacha"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
dependencies = [
"ppv-lite86",
"rand_core 0.6.2",
]
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom 0.1.16",
]
[[package]]
name = "rand_core"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
dependencies = [
"getrandom 0.2.2",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_hc"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
dependencies = [
"rand_core 0.6.2",
]
[[package]]
name = "rayon"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
dependencies = [
"autocfg",
"crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"lazy_static",
"num_cpus",
]
[[package]]
name = "redox_syscall"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
"thread_local",
]
[[package]]
name = "regex-syntax"
version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
[[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.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd281b1030aa675fb90aa994d07187645bb3c8fc756ca766e7c3070b439de9de"
dependencies = [
"base64 0.13.0",
"bytes 1.0.1",
"encoding_rs",
"futures-core",
"futures-util",
"http",
"http-body",
"hyper",
"hyper-rustls",
"hyper-tls",
"ipnet",
"js-sys",
"lazy_static",
"log",
"mime",
"mime_guess",
"native-tls",
"percent-encoding",
"pin-project-lite",
"rustls",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots 0.21.0",
"winreg",
]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]]
name = "rustls"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b"
dependencies = [
"base64 0.13.0",
"log",
"ring",
"sct",
"webpki",
]
[[package]]
name = "ryu"
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 = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "sct"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "security-framework"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1759c2e3c8580017a484a7ac56d3abc5a6c1feadf88db2f3633f12ae4268c69"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f99b9d5e26d2a71633cc4f2ebae7cc9f874044e0c351a27e17892d76dce5678b"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "serde"
version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.8.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23"
dependencies = [
"dtoa",
"linked-hash-map",
"serde",
"yaml-rust",
]
[[package]]
name = "serenity"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dac8367ecfd3380c00dcedf5eb9a47888ae74ae391419b5b1f7735895ed8df4"
dependencies = [
"async-trait",
"async-tungstenite",
"base64 0.13.0",
"bitflags",
"bytes 1.0.1",
"chrono",
"flate2",
"futures",
"percent-encoding",
"reqwest",
"serde",
"serde_json",
"tokio",
"tracing",
"tracing-futures",
"typemap_rev",
"url",
]
[[package]]
name = "sha-1"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f"
dependencies = [
"block-buffer",
"cfg-if",
"cpuid-bool",
"digest",
"opaque-debug",
]
[[package]]
name = "signal-hook-registry"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
dependencies = [
"libc",
]
[[package]]
name = "simplelog"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bc0ffd69814a9b251d43afcabf96dad1b29f5028378056257be9e3fecc9f720"
dependencies = [
"chrono",
"log",
"termcolor",
]
[[package]]
name = "slab"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]]
name = "smallvec"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "socket2"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
dependencies = [
"cfg-if",
"libc",
"winapi",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "syn"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
dependencies = [
"proc-macro2",
"quote",
"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.3",
"redox_syscall",
"remove_dir_all",
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
dependencies = [
"once_cell",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "tinyvec"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8190d04c665ea9e6b6a0dc45523ade572c088d2e6566244c1122671dbf4ae3a"
dependencies = [
"autocfg",
"bytes 1.0.1",
"libc",
"memchr",
"mio",
"num_cpus",
"once_cell",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"tokio-macros",
"winapi",
]
[[package]]
name = "tokio-macros"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57"
dependencies = [
"proc-macro2",
"quote",
"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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
dependencies = [
"rustls",
"tokio",
"webpki",
]
[[package]]
name = "tokio-stream"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1981ad97df782ab506a1f43bf82c967326960d278acf3bf8279809648c3ff3ea"
dependencies = [
"futures-core",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebb7cb2f00c5ae8df755b252306272cd1790d39728363936e01827e11f0b017b"
dependencies = [
"bytes 1.0.1",
"futures-core",
"futures-sink",
"log",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tower-service"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
[[package]]
name = "tracing"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f77d3842f76ca899ff2dbcf231c5c65813dea431301d6eb686279c15c4464f12"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a9bd1db7706f2373a190b0d067146caa39350c486f3d455b0e33b431f94c07"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
dependencies = [
"lazy_static",
]
[[package]]
name = "tracing-futures"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2"
dependencies = [
"pin-project",
"tracing",
]
[[package]]
name = "try-lock"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
[[package]]
name = "tungstenite"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0308d80d86700c5878b9ef6321f020f29b1bb9d5ff3cab25e75e23f3a492a23"
dependencies = [
"base64 0.12.3",
"byteorder",
"bytes 0.5.6",
"http",
"httparse",
"input_buffer",
"log",
"rand 0.7.3",
"sha-1",
"url",
"utf-8",
]
[[package]]
name = "typemap_rev"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335fb14412163adc9ed4a3e53335afaa7a4b72bdd122e5f72f51b8f1db1a131e"
[[package]]
name = "typenum"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "ucc-discord-bot"
version = "0.1.0"
dependencies = [
"aes",
"async-trait",
"base64 0.13.0",
"block-modes",
"chrono",
"diesel",
"guard",
"ical",
"indexmap",
"lazy_static",
"ldap3",
"log",
"rand 0.8.3",
"rayon",
"regex",
"reqwest",
"serde",
"serde_yaml",
"serenity",
"simplelog",
"tokio",
"url",
]
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
"version_check",
]
[[package]]
name = "unicode-bidi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
dependencies = [
"matches",
]
[[package]]
name = "unicode-normalization"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-xid"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
dependencies = [
"form_urlencoded",
"idna",
"matches",
"percent-encoding",
]
[[package]]
name = "utf-8"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
[[package]]
name = "vcpkg"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "want"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
dependencies = [
"log",
"try-lock",
]
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-bindgen"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be"
dependencies = [
"cfg-if",
"serde",
"serde_json",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3de431a2910c86679c34283a33f66f4e4abd7e0aec27b6669060148872aadf94"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64"
[[package]]
name = "web-sys"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "webpki"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f20dea7535251981a9670857150d571846545088359b28e4951d350bdaf179f"
dependencies = [
"webpki",
]
[[package]]
name = "webpki-roots"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82015b7e0b8bad8185994674a13a93306bea76cf5a16c5a181382fd3a5ec2376"
dependencies = [
"webpki",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winreg"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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",
]
......@@ -5,16 +5,25 @@ authors = ["tec <tec@ucc.gu.uwa.edu.au>"]
edition = "2018"
[dependencies]
base64 = "^0.11"
chrono = "^0.4.10"
lazy_static = "^1.4.0"
log = "^0.4.8"
openssl = "^0.10"
rand = "^0.7.2"
serde = "^1.0.104"
serde_yaml = "^0.8"
serenity = "0.8.0"
simplelog = "^0.7.4"
aes = "0.6"
async-trait = "0.1.42"
base64 = "0.13.0"
block-modes = "0.7"
chrono = "0.4.19"
diesel = { version = "1.4.5", features = ["sqlite"] }
guard = "0.5.0"
indexmap = { version = "1.3.1", features = ["serde-1"] }
rayon = "1.3.0"
ical = "0.7.0"
indexmap = { version = "1.6.1", features = ["serde-1"] }
lazy_static = "1.4.0"
ldap3 = "0.9.1"
log = "0.4.11"
rand = "0.8.1"
rayon = "1.5.0"
regex = "1.4.3"
reqwest = "0.11.0"
serde = "1.0.118"
serde_yaml = "0.8.15"
serenity = { version = "0.10.1", default-features = false, features = ["builder", "cache", "client", "gateway", "model", "http", "utils", "rustls_backend"]}
simplelog = "0.9.0"
tokio = { version = "1", features = ["full"] }
url = "2.2.0"
File added
......@@ -7,6 +7,8 @@ use std::fs;
lazy_static! {
static ref CONFIG_FILE: String = fs::read_to_string("config.yml").unwrap();
pub static ref CONFIG: UccbotConfig = serde_yaml::from_str(&CONFIG_FILE).unwrap();
static ref SECRETS_FILE: String = fs::read_to_string("secrets.yml").unwrap();
pub static ref SECRETS: UccbotSecrets = serde_yaml::from_str(&SECRETS_FILE).unwrap();
}
#[derive(Debug, Deserialize)]
......@@ -15,12 +17,15 @@ pub struct UccbotConfig {
pub main_channel: id::ChannelId,
pub welcome_channel: id::ChannelId,
pub announcement_channel: id::ChannelId,
pub bot_id: u64,
pub readme_channel: id::ChannelId,
pub bot_id: id::UserId,
pub ical_url: Option<String>,
pub vote_pool_size: i8,
pub vote_role: u64,
pub tiebreaker_role: u64,
pub unregistered_member_role: u64,
pub registered_member_role: u64,
pub expired_member_role: u64,
pub command_prefix: String,
pub for_vote: String,
pub against_vote: String,
......@@ -29,6 +34,12 @@ pub struct UccbotConfig {
pub disapprove_react: String,
pub unsure_react: String,
pub react_role_messages: Vec<ReactionMapping>,
#[serde(default = "ldap_bind_address")]
pub bind_address: String,
}
pub fn ldap_bind_address() -> String {
"ldaps://samson.ucc.asn.au:636".to_string()
}
impl UccbotConfig {
......@@ -44,6 +55,12 @@ impl UccbotConfig {
}
}
#[derive(Debug, Deserialize)]
pub struct UccbotSecrets {
pub ldap_pass: String,
pub discord_token: String,
}
pub type ReactRoleMap = IndexMap<String, id::RoleId>;
#[derive(Debug, Deserialize, Clone)]
......
......@@ -2,6 +2,7 @@ server_id: 606351521117896704 # general
main_channel: 606351521117896706 # the-corner
welcome_channel: 606351613816209418 # general
announcement_channel: 606351521117896706 # the-corner
readme_channel: 606351613816209418 # general
bot_id: 607078903969742848
......@@ -10,6 +11,7 @@ vote_role: 607478818038480937 # Vote Role
tiebreaker_role: 607509283483025409 # tie-breaker
unregistered_member_role: 608282247350714408 # unregistered
registered_member_role: 608282133118582815 # registered
expired_member_role: 607479030370926613 # registered
command_prefix: "!"
for_vote: "👍"
......
......@@ -2,14 +2,17 @@ server_id: 264401248676085760 # ucc
main_channel: 264401248676085760 # ucc
welcome_channel: 606750983699300372 # welcome
announcement_channel: 264411219627212801 # committee
readme_channel: 674252245008908298 # readme
bot_id: 635407267881156618
ical_url: "https://calendar.google.com/calendar/ical/rb44is9l4dftsnk6lmf1qske6g%40group.calendar.google.com/public/basic.ics"
vote_pool_size: 7 # 4 exec + 3 ocm
vote_pool_size: 8 # 4 exec + Fresher rep + 3 ocm
vote_role: 269817189966544896 # @committee
tiebreaker_role: 635370432568098817 # @Presiding Presidenterino
unregistered_member_role: 0 # does not exist
registered_member_role: 0 # does not exist
tiebreaker_role: 0 # No tiebreak apparently 635370432568098817 # @Presiding Presidenterino
unregistered_member_role: 674641042464833548 # @unregistered
registered_member_role: 692754285557055490 # @member
expired_member_role: 692754285557055490 # @member
command_prefix: "!"
......@@ -21,12 +24,27 @@ disapprove_react: "⬇"
unsure_react: "❔"
react_role_messages:
- message: 674254534859554837 # Club-related
- message: 674652973103579225 # Intro
- message: 674653014119809025 # Club-related
mapping:
uwa: 674254888221278226
uccl: 674255044006117386
🥤: 691852097003585577
cpu: 674255083063738368
- message: 674254563083288590 # Operating System
🎮: 691852441091833896
🤖: 696241187035676763
📐: 696241431550885888
🧬: 696241817397624862
: 696241928492286044
🧮: 696242272844382209
💸: 696242320609378345
📚: 696257213068607518
🎨: 696242397407084614
: 696242439962230854
🌥️: 696242479527100506
: 696242521738838016
🌤: 696242572288589864
: 696242617301860403
- message: 674653030817464370 # Operating System
mapping:
win10: 674255085307691039
capple: 674255209916137492
......@@ -35,7 +53,7 @@ react_role_messages:
fedora: 674260517858181122
linux: 674255299494019093
bsd: 674255522433859604
- message: 674254593127088148 # Editor
- message: 674653049578455060 # Editor
mapping:
vim: 674255633356423208
emacs: 674255739061010443
......@@ -44,7 +62,8 @@ react_role_messages:
atom_: 674255748426891274
intellij: 674255748959567901
npp: 674255750297812993
- message: 674254633929277440 # Programming
eclipse: 696281046924394526
- message: 674653069526695946 # Programming (1)
mapping:
html: 674255751031685130
css: 674255751707099136
......@@ -56,6 +75,8 @@ react_role_messages:
julia: 674255755305680901
matlab: 674255755809128448
mathematica: 674255756320833536
- message: 674653087075532810 # Programming (2)
mapping:
c_: 674256084403224673
cpp: 674256084826849281
csharp: 674256086261563442
......@@ -65,7 +86,12 @@ react_role_messages:
haskell: 674256088861769738
ruby: 674256089742835712
scala: 674256090321387530
go: 674256091441397761
lua: 674256090795474944
go: 674256091441397761
tex: 674256092187983897
git: 674256093064593439
- message: 674653104666312704 # games
mapping:
et: 728997559061708922
factorio: 728997562710884373
minecraft: 728997565496033320
......@@ -13,6 +13,7 @@ vote_role: 607478818038480937
tiebreaker_role: 607509283483025409
unregistered_member_role: 608282247350714408
registered_member_role: 608282133118582815
expired_member_role: 0
command_prefix: "!"
for_vote: "👍"
......
use diesel::prelude::*;
use diesel::result::Error;
use diesel::sqlite::SqliteConnection;
// TODO reuse DB connection, using r2d2 or something
use crate::ldap::*;
#[table_name = "members"]
#[derive(Queryable, AsChangeset, Insertable)]
pub struct Member {
pub discord_id: i64,
pub tla: Option<String>,
pub username: String,
pub member_since: Option<String>,
pub name: Option<String>,
pub biography: Option<String>,
pub github: Option<String>,
pub photo: Option<String>,
pub website: Option<String>,
pub study: Option<String>,
}
table! {
members (discord_id) {
discord_id -> BigInt,
tla -> Nullable<Text>,
username -> Text,
member_since -> Nullable<Text>,
name -> Nullable<Text>,
biography -> Nullable<Text>,
github -> Nullable<Text>,
photo -> Nullable<Text>,
website -> Nullable<Text>,
study -> Nullable<Text>,
}
}
pub fn db_connection() -> SqliteConnection {
SqliteConnection::establish("state.db").expect("Failed to connect to sqlite DB")
}
pub fn add_member(discord_id: &u64, username: &str) -> Member {
let ldap_user = ldap_search(username);
let name = ldap_user.as_ref().map(|u| u.name.clone());
let tla_user = tla_search(username);
let tla = tla_user.as_ref().map(|u| u.tla.clone()).flatten();
let new_member = Member {
discord_id: *discord_id as i64,
username: username.to_string(),
name: name.clone(),
tla: tla,
member_since: None,
biography: None,
github: None,
photo: None,
website: None,
study: None,
};
diesel::insert_into(members::table)
.values(&new_member)
.execute(&db_connection())
.expect("Failed to add member to DB");
info!(
"{} added to member DB",
name.unwrap_or(discord_id.to_string())
);
new_member
}
#[allow(dead_code)] // remove this if you start using it
pub fn update_member(discord_id: &u64, member: Member) -> Result<usize, Error> {
diesel::update(members::table.find(*discord_id as i64))
.set(&member)
.execute(&db_connection())
}
pub fn username_exists(username: &str) -> bool {
get_member_info_from_username(username).is_ok()
}
pub fn get_member_info(discord_id: &u64) -> Result<Member, Error> {
members::table
.find(*discord_id as i64)
.first(&db_connection())
}
pub fn get_member_info_from_username(username: &str) -> Result<Member, Error> {
members::table
.filter(members::username.eq(username))
.first(&db_connection())
}
pub fn get_member_info_from_tla(tla: &str) -> Result<Member, Error> {
members::table
.filter(members::tla.eq(tla))
.first(&db_connection())
}
pub fn set_member_bio(discord_id: &u64, bio: Option<&str>) -> Result<usize, Error> {
diesel::update(members::table.find(*discord_id as i64))
.set(members::biography.eq(bio))
.execute(&db_connection())
}
pub fn set_member_git(discord_id: &u64, git: Option<&str>) -> Result<usize, Error> {
diesel::update(members::table.find(*discord_id as i64))
.set(members::github.eq(git))
.execute(&db_connection())
}
pub fn set_member_photo(discord_id: &u64, url: Option<&str>) -> Result<usize, Error> {
diesel::update(members::table.find(*discord_id as i64))
.set(members::photo.eq(url))
.execute(&db_connection())
}
pub fn set_member_website(discord_id: &u64, url: Option<&str>) -> Result<usize, Error> {
diesel::update(members::table.find(*discord_id as i64))
.set(members::website.eq(url))
.execute(&db_connection())
}
pub fn set_member_study(discord_id: &u64, study: Option<&str>) -> Result<usize, Error> {
diesel::update(members::table.find(*discord_id as i64))
.set(members::study.eq(study))
.execute(&db_connection())
}
use crate::config::{CONFIG, SECRETS};
use ldap3::{LdapConn, LdapConnSettings, Scope, SearchEntry};
#[derive(Debug)]
pub struct LDAPUser {
pub username: String,
pub name: String,
pub when_created: String,
pub login_shell: String,
}
pub fn ldap_search(username: &str) -> Option<LDAPUser> {
let settings = LdapConnSettings::new().set_no_tls_verify(true);
let mut ldap =
LdapConn::with_settings(settings, &CONFIG.bind_address).expect("Unable to connect to LDAP");
ldap.simple_bind(
"cn=ucc-discord-bot,cn=Users,dc=ad,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au",
&SECRETS.ldap_pass,
)
.expect("Unable to attempt to bind to LDAP")
.success()
.expect("Unable to bind to LDAP");
let (rs, _res) = ldap
.search(
"cn=Users,dc=ad,dc=ucc,dc=gu,dc=uwa,dc=edu,dc=au",
Scope::Subtree,
&format!("(cn={})", ldap3::ldap_escape(username)),
vec!["when_created", "displayName", "name", "loginShell"],
)
.expect("LDAP error")
.success()
.expect("LDAP search error");
if rs.is_empty() {
return None;
}
let result = SearchEntry::construct(rs[0].clone()).attrs;
Some(LDAPUser {
username: result
.get("name")
.expect("LDAP failed to get 'name' field")
.join(""),
name: result
.get("displayName")
.expect("LDAP failed to get 'displayName' field")
.join(""),
when_created: "".to_string(), // result
// .get("whenCreated")
// .expect("LDAP failed to get 'whenCreated' field")
// .join(""),
login_shell: result
.get("loginShell")
.expect("LDAP failed to get 'loginShell' field")
.join(""),
})
}
pub fn ldap_exists(username: &str) -> bool {
ldap_search(username).is_some()
}
#[derive(Debug)]
pub struct TLA {
pub tla: Option<String>,
pub name: String,
pub username: String,
}
pub fn tla_search(term: &str) -> Option<TLA> {
let tla_search = String::from_utf8(
std::process::Command::new("tla")
.arg(term)
.output()
.expect("failed to execute tla")
.stdout,
)
.expect("unable to parse stdout to String");
let tla_results = tla_search.split("\n").collect::<Vec<&str>>();
if tla_results.len() != 4 {
return None;
}
let mut the_tla = Some(tla_results[0].replace("TLA: ", "")[1..4].to_string());
if the_tla == Some(String::from("???")) {
the_tla = None;
}
Some(TLA {
tla: the_tla,
name: tla_results[1].replace("Name: ", ""),
username: tla_results[2].replace("Login: ", ""),
})
}
......@@ -7,202 +7,38 @@ extern crate indexmap;
extern crate simplelog;
#[macro_use]
extern crate guard;
#[macro_use]
extern crate diesel;
extern crate ldap3;
extern crate reqwest;
extern crate tokio;
use simplelog::*;
use std::fs::{read_to_string, File};
use std::fs::File;
use chrono::prelude::Utc;
use serenity::{
model::{channel, channel::Message, gateway::Ready, guild::Member},
prelude::*,
utils::MessageBuilder,
};
use serenity::client::Client;
#[macro_use]
mod util;
mod config;
mod database;
mod ldap;
mod reaction_roles;
mod serenity_handler;
mod token_management;
mod user_management;
mod util;
mod voting;
use config::CONFIG;
use reaction_roles::{add_role_by_reaction, remove_role_by_reaction};
use util::get_string_from_react;
use config::SECRETS;
use serenity_handler::Handler;
macro_rules! e {
($error: literal, $x:expr) => {
match $x {
Ok(_) => (),
Err(why) => error!($error, why),
}
};
}
struct Handler;
impl EventHandler for Handler {
// Set a handler for the `message` event - so that whenever a new message
// is received - the closure (or function) passed will be called.
//
// Event handlers are dispatched through a threadpool, and so multiple
// events can be dispatched simultaneously.
fn message(&self, ctx: Context, msg: Message) {
if !(msg.content.starts_with(&CONFIG.command_prefix)) {
return;
}
let message_content: Vec<_> = msg.content[1..].splitn(2, ' ').collect();
match message_content[0] {
"say" => {
println!("{:#?}", msg.content);
}
"register" => user_management::Commands::register(ctx, msg.clone(), message_content[1]),
"verify" => user_management::Commands::verify(ctx, msg.clone(), message_content[1]),
"move" => {
voting::Commands::move_something(ctx, msg.clone(), message_content[1]);
}
"motion" => {
voting::Commands::motion(ctx, msg.clone(), message_content[1]);
}
"poll" => {
voting::Commands::poll(ctx, msg.clone(), message_content[1]);
}
"cowsay" => {
voting::Commands::cowsay(ctx, msg.clone(), message_content[1]);
}
"logreact" => {
e!("Error deleting logreact prompt: {:?}", msg.delete(&ctx));
e!(
"Error sending message {:?}",
msg.channel_id
.say(&ctx.http, "React to this to log the ID (for the next 5min)")
)
}
"help" => {
let mut message = MessageBuilder::new();
message.push_line(format!(
"Use {}move <action> to make a circular motion",
&CONFIG.command_prefix
));
message.push_line(format!(
"Use {}poll <proposal> to see what people think about something",
&CONFIG.command_prefix
));
e!(
"Error sending message: {:?}",
msg.channel_id.say(&ctx.http, message.build())
);
}
_ => {
e!(
"Error sending message: {:?}",
msg.channel_id.say(
&ctx.http,
format!("Unrecognised command. Try {}help", &CONFIG.command_prefix)
)
);
}
}
}
fn reaction_add(&self, ctx: Context, add_reaction: channel::Reaction) {
match add_reaction.message(&ctx.http) {
Ok(message) => {
let message_type = get_message_type(&message);
if message_type == MessageType::RoleReactMessage
&& add_reaction.user_id.0 != CONFIG.bot_id
{
add_role_by_reaction(&ctx, message, add_reaction);
return;
}
if message.author.id.0 != CONFIG.bot_id || add_reaction.user_id == CONFIG.bot_id {
return;
}
match message_type {
MessageType::Motion => {
voting::reaction_add(ctx, add_reaction);
}
MessageType::LogReact => {
let react_user = add_reaction.user(&ctx).unwrap();
let react_as_string = get_string_from_react(&add_reaction.emoji);
if Utc::now().timestamp() - message.timestamp.timestamp() > 300 {
warn!(
"The logreact message {} just tried to use is too old",
react_user.name
);
return;
}
info!(
"The react {} just added is {:?}. In full: {:?}",
react_user.name, react_as_string, add_reaction.emoji
);
let mut msg = MessageBuilder::new();
msg.push_italic(react_user.name);
msg.push(format!(
" wanted to know that {} is represented by ",
add_reaction.emoji,
));
msg.push_mono(react_as_string);
e!(
"Error sending message: {:?}",
message.channel_id.say(&ctx.http, msg.build())
);
}
_ => {}
}
}
Err(why) => error!("Failed to get react message {:?}", why),
}
}
fn reaction_remove(&self, ctx: Context, removed_reaction: channel::Reaction) {
match removed_reaction.message(&ctx.http) {
Ok(message) => {
let message_type = get_message_type(&message);
if message_type == MessageType::RoleReactMessage
&& removed_reaction.user_id != CONFIG.bot_id
{
remove_role_by_reaction(&ctx, message, removed_reaction);
return;
}
if message.author.id.0 != CONFIG.bot_id || removed_reaction.user_id == CONFIG.bot_id
{
return;
}
if message_type == MessageType::Motion {
voting::reaction_remove(ctx, removed_reaction);
}
}
Err(why) => error!("Failed to get react message {:?}", why),
}
}
fn guild_member_addition(
&self,
ctx: Context,
_guild_id: serenity::model::id::GuildId,
the_new_member: Member,
) {
user_management::new_member(&ctx, the_new_member);
}
// Set a handler to be called on the `ready` event. This is called when a
// shard is booted, and a READY payload is sent by Discord. This payload
// contains data like the current user's guild Ids, current user data,
// private channels, and more.
//
// In this case, just print what the current user's username is.
fn ready(&self, ctx: Context, ready: Ready) {
info!("{} is connected!", ready.user.name);
reaction_roles::sync_all_role_reactions(&ctx);
}
fn resume(&self, ctx: Context, _: serenity::model::event::ResumedEvent) {
reaction_roles::sync_all_role_reactions(&ctx);
}
}
fn main() {
#[tokio::main]
async fn main() {
CombinedLogger::init(vec![
TermLogger::new(LevelFilter::Info, Config::default(), TerminalMode::Mixed).unwrap(),
TermLogger::new(LevelFilter::Info, Config::default(), TerminalMode::Mixed),
WriteLogger::new(
LevelFilter::Info,
Config::default(),
......@@ -211,55 +47,21 @@ fn main() {
])
.unwrap();
// Configure the client with your Discord bot token in the environment.
let token = read_to_string("discord_token").unwrap();
// ical::fetch_latest_ical().wait();
// Create a new instance of the Client, logging in as a bot. This will
// automatically prepend your bot token with "Bot ", which is a requirement
// by Discord for bot users.
let mut client = Client::new(&token, Handler).expect("Err creating client");
let mut client = Client::builder(&SECRETS.discord_token)
.event_handler(Handler)
.await
.expect("Err creating client");
// Finally, start a single shard, and start listening to events.
//
// Shards will automatically attempt to reconnect, and will perform
// exponential backoff until it reconnects.
if let Err(why) = client.start() {
if let Err(why) = client.start().await {
error!("Client error: {:?}", why);
}
}
#[derive(Debug, PartialEq)]
enum MessageType {
Motion,
Role,
RoleReactMessage,
LogReact,
Poll,
Misc,
}
fn get_message_type(message: &Message) -> MessageType {
if CONFIG
.react_role_messages
.iter()
.any(|rrm| rrm.message == message.id)
{
return MessageType::RoleReactMessage;
}
if message.embeds.is_empty() {
// Get first word of message
return match message.content.splitn(2, ' ').next().unwrap() {
"Role" => MessageType::Role,
"React" => MessageType::LogReact,
_ => MessageType::Misc,
};
}
let title: String = message.embeds[0].title.clone().unwrap();
let words_of_title: Vec<_> = title.splitn(2, ' ').collect();
let first_word_of_title = words_of_title[0];
match first_word_of_title {
"Motion" => MessageType::Motion,
"Poll" => MessageType::Poll,
_ => MessageType::Misc,
}
}
use crate::config::{ReactRoleMap, CONFIG};
use crate::util::{get_react_from_string, get_string_from_react};
use rayon::prelude::*;
use serenity::{
client::Context,
model::{channel::Message, channel::Reaction, id::RoleId, id::UserId},
......@@ -8,19 +7,12 @@ use serenity::{
use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
macro_rules! e {
($error: literal, $x:expr) => {
match $x {
Ok(_) => (),
Err(why) => error!($error, why),
}
};
}
pub fn add_role_by_reaction(ctx: &Context, msg: Message, added_reaction: Reaction) {
pub async fn add_role_by_reaction(ctx: &Context, msg: Message, added_reaction: Reaction) {
let user = added_reaction
.user_id
.unwrap()
.to_user(ctx)
.await
.expect("Unable to get user");
if let Some(role_id) = CONFIG
.react_role_messages
......@@ -36,24 +28,29 @@ pub fn add_role_by_reaction(ctx: &Context, msg: Message, added_reaction: Reactio
user.name,
role_id
.to_role_cached(ctx)
.await
.expect("Unable to get role")
.name
);
ctx.http
.add_member_role(
CONFIG.server_id,
added_reaction.user_id.0,
*added_reaction.user_id.unwrap().as_u64(),
*role_id.as_u64(),
)
.await
.ok();
} else {
warn!("{} provided invalid react for role", user.name);
e!("Unable to delete react: {:?}", added_reaction.delete(ctx));
e!(
"Unable to delete react: {:?}",
added_reaction.delete(ctx).await
);
}
}
pub fn remove_role_by_reaction(ctx: &Context, msg: Message, removed_reaction: Reaction) {
CONFIG
pub async fn remove_role_by_reaction(ctx: &Context, msg: Message, removed_reaction: Reaction) {
let role_id = CONFIG
.react_role_messages
.iter()
.find(|rrm| rrm.message == msg.id)
......@@ -61,30 +58,31 @@ pub fn remove_role_by_reaction(ctx: &Context, msg: Message, removed_reaction: Re
let react_as_string = get_string_from_react(&removed_reaction.emoji);
reaction_mapping.mapping.get(&react_as_string)
})
.and_then(|role_id| {
info!(
"{} requested removal of role '{}'",
msg.author.name,
role_id
.to_role_cached(ctx)
.expect("Unable to get role")
.name
);
ctx.http
.remove_member_role(
CONFIG.server_id,
removed_reaction.user_id.0,
*role_id.as_u64(),
)
.ok()
});
.unwrap();
info!(
"{} requested removal of role '{}'",
msg.author.name,
role_id
.to_role_cached(ctx)
.await
.expect("Unable to get role")
.name
);
ctx.http
.remove_member_role(
CONFIG.server_id,
*removed_reaction.user_id.unwrap().as_u64(),
*role_id.as_u64(),
)
.await
.ok();
}
pub fn sync_all_role_reactions(ctx: &Context) {
pub async fn sync_all_role_reactions(ctx: &Context) {
info!("Syncing roles to reactions");
let messages_with_role_mappings = get_all_role_reaction_message(ctx);
info!(" Sync: reaction messages fetched");
let guild = ctx.http.get_guild(CONFIG.server_id).unwrap();
let guild = ctx.http.get_guild(CONFIG.server_id).await.unwrap();
info!(" Sync: guild fetched");
// this method supports paging, but we probably don't need it since the server only has a couple of
// hundred members. the Reaction.users() method can apparently only retrieve 100 users at once, but
......@@ -92,18 +90,17 @@ pub fn sync_all_role_reactions(ctx: &Context) {
let mut all_members = ctx
.http
.get_guild_members(CONFIG.server_id, Some(1000), None)
.await
.unwrap();
all_members.retain(|m| m.user_id() != CONFIG.bot_id);
all_members.retain(|m| m.user.id != CONFIG.bot_id);
info!(" Sync: all members fetched");
let mut roles_to_add: HashMap<UserId, Vec<RoleId>> =
HashMap::from_iter(all_members.iter().map(|m| (m.user_id(), Vec::new())));
HashMap::from_iter(all_members.iter().map(|m| (m.user.id, Vec::new())));
let mut roles_to_remove: HashMap<UserId, Vec<RoleId>> =
HashMap::from_iter(all_members.iter().map(|m| (m.user_id(), Vec::new())));
HashMap::from_iter(all_members.iter().map(|m| (m.user.id, Vec::new())));
let mut i = 0;
for (message, mapping) in messages_with_role_mappings {
i += 1;
for (i, (message, mapping)) in messages_with_role_mappings.await.iter().enumerate() {
info!(" Sync: prossessing message #{}", i);
for react in &message.reactions {
let react_as_string = get_string_from_react(&react.reaction_type);
......@@ -114,18 +111,30 @@ pub fn sync_all_role_reactions(ctx: &Context) {
" message #{}: Removing non-role react '{}'",
i, react_as_string
);
for _illegal_react in
&message.reaction_users(ctx, react.reaction_type.clone(), Some(100), None)
for illegal_react_user in &message
.reaction_users(&ctx.http, react.reaction_type.clone(), Some(100), None)
.await
.unwrap_or(vec![])
{
warn!(" need to implement react removal");
message
.channel_id
.delete_reaction(
&ctx.http,
message.id,
Some(illegal_react_user.id),
react.reaction_type.clone(),
)
.await
.expect("Unable to delete react");
}
}
for (react, role) in mapping {
for (react, role) in *mapping {
info!(" message #{}: processing react '{}'", i, react);
// TODO: proper pagination for the unlikely scenario that there are more than 100 (255?) reactions?
let reaction_type = get_react_from_string(react.clone(), guild.clone());
let reactors = message
.reaction_users(ctx.http.clone(), reaction_type.clone(), Some(100), None)
.await
.unwrap();
let reactor_ids: HashSet<UserId> = HashSet::from_iter(reactors.iter().map(|r| r.id));
......@@ -133,12 +142,12 @@ pub fn sync_all_role_reactions(ctx: &Context) {
if !reactor_ids.contains(&UserId::from(CONFIG.bot_id)) {
e!(
"Unable to add reaction, {:?}",
message.react(ctx, reaction_type)
message.react(ctx, reaction_type).await
);
}
for member in all_members.clone() {
let user_id = &member.user_id();
let user_id = &member.user.id;
if reactor_ids.contains(&user_id) {
if !member.roles.iter().any(|r| r == role) {
roles_to_add.get_mut(&user_id).unwrap().push(*role);
......@@ -155,10 +164,13 @@ pub fn sync_all_role_reactions(ctx: &Context) {
if !roles.is_empty() {
let mut member = all_members
.iter()
.find(|m| m.user_id() == user_id)
.find(|m| m.user.id == user_id)
.unwrap()
.clone();
member.add_roles(ctx.http.clone(), &roles[..]).unwrap();
member
.add_roles(ctx.http.clone(), &roles[..])
.await
.unwrap();
}
}
info!(" Sync: (any) missing roles added");
......@@ -166,36 +178,36 @@ pub fn sync_all_role_reactions(ctx: &Context) {
if !roles.is_empty() {
let mut member = all_members
.iter()
.find(|m| m.user_id() == user_id)
.find(|m| m.user.id == user_id)
.unwrap()
.clone();
member.remove_roles(ctx.http.clone(), &roles[..]).unwrap();
member
.remove_roles(ctx.http.clone(), &roles[..])
.await
.unwrap();
}
}
info!(" Sync: (any) superflous roles removed");
info!("Role reaction sync complete");
}
fn get_all_role_reaction_message(ctx: &Context) -> Vec<(Message, &'static ReactRoleMap)> {
let guild = ctx.http.get_guild(CONFIG.server_id).unwrap();
async fn get_all_role_reaction_message(ctx: &Context) -> Vec<(Message, &'static ReactRoleMap)> {
let guild = ctx.http.get_guild(CONFIG.server_id).await.unwrap();
info!(" Find role-react message: guild determined");
let channels = ctx.http.get_channels(*guild.id.as_u64()).unwrap();
let channels = ctx.http.get_channels(*guild.id.as_u64()).await.unwrap();
info!(" Find role-react message: channels determined");
let http = ctx.http.clone();
channels
.par_iter()
.flat_map(|channel| {
// since we don't know which channels the messages are in, we check every combination
// of message and channel and ignore the bad matches using .ok() and .filter_map()
let h = http.clone(); // thread-local copy
CONFIG
.react_role_messages
.par_iter()
.filter_map(move |rrm| {
h.get_message(*channel.id.as_u64(), *rrm.message.as_u64())
.ok()
.map(|m| (m, &rrm.mapping))
})
})
.collect()
let mut v = Vec::new();
for channel in channels {
for reaction in CONFIG.react_role_messages.iter() {
if let Some(m) = http
.get_message(*channel.id.as_u64(), *reaction.message.as_u64())
.await
.ok()
{
v.push((m, &reaction.mapping))
}
}
}
v
}
use async_trait::async_trait;
use chrono::prelude::Utc;
use serenity::{
model::{channel, channel::Message, gateway::Ready, guild::Member},
prelude::*,
utils::MessageBuilder,
};
// use rand::seq::SliceRandom;
use crate::config::CONFIG;
use crate::ldap;
use crate::reaction_roles::{
add_role_by_reaction, remove_role_by_reaction, sync_all_role_reactions,
};
use crate::user_management;
use crate::util::get_string_from_react;
use crate::voting;
pub struct Handler;
#[async_trait]
impl EventHandler for Handler {
// Set a handler for the `message` event - so that whenever a new message
// is received - the closure (or function) passed will be called.
//
// Event handlers are dispatched through a threadpool, and so multiple
// events can be dispatched simultaneously.
async fn message(&self, ctx: Context, msg: Message) {
if !(msg.content.starts_with(&CONFIG.command_prefix)) {
if msg.content.contains(&format!("<@!{}>", CONFIG.bot_id)) // desktop mentions
|| msg.content.contains(&format!("<@{}>", CONFIG.bot_id))
// mobile mentions
{
send_message!(
msg.channel_id,
&ctx.http,
MENTION_RESPONSES[0] //.choose(&mut rand::random())
//.expect("We couldn't get any sass")
);
}
return;
}
let message_content: Vec<_> = msg.content[1..].splitn(2, ' ').collect();
let content = if message_content.len() > 1 {
message_content[1]
} else {
""
};
match message_content[0] {
"say" => println!("{:#?}", msg.content),
"register" => user_management::Commands::register(ctx, msg.clone(), content).await,
"verify" => user_management::Commands::verify(ctx, msg.clone(), content).await,
"profile" => user_management::Commands::profile(ctx, msg.clone(), content).await,
"set" => user_management::Commands::set_info(ctx, msg.clone(), content).await,
"clear" => user_management::Commands::clear_info(ctx, msg.clone(), content).await,
"move" => voting::Commands::move_something(ctx, msg.clone(), content).await,
"motion" => voting::Commands::motion(ctx, msg.clone(), content).await,
"poll" => voting::Commands::poll(ctx, msg.clone(), content).await,
"cowsay" => voting::Commands::cowsay(ctx, msg.clone(), content).await,
"source" => {
let mut mesg = MessageBuilder::new();
mesg.push(
"You want to look at my insides!? Eurgh.\nJust kidding, you can go over ",
);
mesg.push_italic("every inch");
mesg.push(" of me here: https://gitlab.ucc.asn.au/UCC/discord-bot 😉");
send_message!(msg.channel_id, &ctx.http, mesg.build());
}
"help" => {
// Plaintext version, keep in case IRC users kick up a fuss
// let mut message = MessageBuilder::new();
// message.push_line(format!(
// "Use {}move <action> to make a circular motion",
// &CONFIG.command_prefix
// ));
// message.push_line(format!(
// "Use {}poll <proposal> to see what people think about something",
// &CONFIG.command_prefix
// ));
// send_message!(msg.channel_id, &ctx.http, message.build());
let result = msg.channel_id.send_message(&ctx.http, |m| {
m.embed(|embed| {
embed.colour(serenity::utils::Colour::DARK_GREY);
embed.title("Commands for the UCC Bot");
embed.field("About", "This is UCC's own little in-house bot, please treat it nicely :)", false);
embed.field("Commitee", "`!move <text>` to make a circular motion\n\
`!poll <text>` to get people's opinions on something", false);
embed.field("Account", "`!register <ucc username>` to link your Discord and UCC account\n\
`!profile <user>` to get the profile of a user\n\
`!set <bio|git|web|photo>` to set that property of _your_ profile\n\
`!updateroles` to update your registered roles", false);
embed.field("Fun", "`!cowsay <text>` to have a cow say your words\n\
with no `<text>` it'll give you a fortune 😉", false);
embed
});
m
});
if let Err(why) = result.await {
error!("Error sending help embed: {:?}", why);
}
}
// undocumented (in !help) functins
"logreact" => {
e!(
"Error deleting logreact prompt: {:?}",
msg.delete(&ctx).await
);
send_message!(
msg.channel_id,
&ctx.http,
"React to this to log the ID (for the next 5min)"
);
}
"ldap" => send_message!(
msg.channel_id,
&ctx.http,
format!("{:?}", ldap::ldap_search(message_content[1]))
),
"tla" => send_message!(
msg.channel_id,
&ctx.http,
format!("{:?}", ldap::tla_search(message_content[1]))
),
"updateroles" => user_management::Commands::update_registered_role(ctx, msg).await,
_ => send_message!(
msg.channel_id,
&ctx.http,
format!("Unrecognised command. Try {}help", &CONFIG.command_prefix)
),
}
}
async fn reaction_add(&self, ctx: Context, add_reaction: channel::Reaction) {
match add_reaction.message(&ctx.http).await {
Ok(message) => match get_message_type(&message) {
MessageType::RoleReactMessage if add_reaction.user_id == Some(CONFIG.bot_id) => {
add_role_by_reaction(&ctx, message, add_reaction).await
}
_ if message.author.id != CONFIG.bot_id
|| add_reaction.user_id == Some(CONFIG.bot_id) => {}
MessageType::Motion => voting::reaction_add(&ctx, add_reaction).await,
MessageType::LogReact => {
let react_user = add_reaction.user(&ctx).await.unwrap();
let react_as_string = get_string_from_react(&add_reaction.emoji);
if Utc::now().timestamp() - message.timestamp.timestamp() > 300 {
warn!(
"The logreact message {} just tried to use is too old",
react_user.name
);
return;
}
info!(
"The react {} just added is {:?}. In full: {:?}",
react_user.name, react_as_string, add_reaction.emoji
);
let mut msg = MessageBuilder::new();
msg.push_italic(react_user.name);
msg.push(format!(
" wanted to know that {} is represented by ",
add_reaction.emoji,
));
msg.push_mono(react_as_string);
message
.channel_id
.say(&ctx.http, msg.build())
.await
.unwrap();
send_message!(message.channel_id, &ctx.http, msg.build());
}
_ => {}
},
Err(why) => error!("Failed to get react message {:?}", why),
}
}
async fn reaction_remove(&self, ctx: Context, removed_reaction: channel::Reaction) {
match removed_reaction.message(&ctx.http).await {
Ok(message) => match get_message_type(&message) {
MessageType::RoleReactMessage
if removed_reaction.user_id.unwrap() != CONFIG.bot_id =>
{
remove_role_by_reaction(&ctx, message, removed_reaction).await
}
_ if message.author.id != CONFIG.bot_id
|| removed_reaction.user_id.unwrap() == CONFIG.bot_id => {}
MessageType::Motion => voting::reaction_remove(&ctx, removed_reaction).await,
_ => {}
},
Err(why) => error!("Failed to get react message {:?}", why),
}
}
async fn guild_member_addition(
&self,
ctx: Context,
_guild_id: serenity::model::id::GuildId,
the_new_member: Member,
) {
user_management::new_member(&ctx, the_new_member).await
}
// Set a handler to be called on the `ready` event. This is called when a
// shard is booted, and a READY payload is sent by Discord. This payload
// contains data like the current user's guild Ids, current user data,
// private channels, and more.
//
// In this case, just print what the current user's username is.
async fn ready(&self, ctx: Context, ready: Ready) {
info!("{} is connected!", ready.user.name);
sync_all_role_reactions(&ctx).await
}
async fn resume(&self, ctx: Context, _: serenity::model::event::ResumedEvent) {
sync_all_role_reactions(&ctx).await
}
}
pub const MENTION_RESPONSES: &[&str] = &[
"Oh hello there",
"Stop bothering me. I'm busy.",
"You know, I'm trying to keep track of this place. I don't need any more distractions.",
"Don't you have better things to do?",
"(sigh) what now?",
"Yes, yes, I know I'm brilliant",
"What do I need to do to catch a break around here? Eh.",
"Mmmmhmmm. I'm still around, don't mind me.",
"You know, some people would consider this rude. Luckily I'm not one of those people. In fact, I'm not even a person.",
"Perhaps try bothering someone else for a change."
];
#[derive(Debug, PartialEq)]
enum MessageType {
Motion,
Role,
RoleReactMessage,
LogReact,
Poll,
Misc,
}
fn get_message_type(message: &Message) -> MessageType {
if CONFIG
.react_role_messages
.iter()
.any(|rrm| rrm.message == message.id)
{
return MessageType::RoleReactMessage;
}
if message.embeds.is_empty() {
// Get first word of message
return match message.content.splitn(2, ' ').next().unwrap() {
"Role" => MessageType::Role,
"React" => MessageType::LogReact,
_ => MessageType::Misc,
};
}
let title: String = message.embeds[0].title.clone().unwrap();
let words_of_title: Vec<_> = title.splitn(2, ' ').collect();
let first_word_of_title = words_of_title[0];
match first_word_of_title {
"Motion" => MessageType::Motion,
"Poll" => MessageType::Poll,
_ => MessageType::Misc,
}
}
use aes::Aes128;
use base64;
use block_modes::block_padding::Pkcs7;
use block_modes::{BlockMode, Cbc};
use chrono::{prelude::Utc, DateTime};
use openssl::symm::{decrypt, encrypt, Cipher};
use rand::Rng;
use serenity::model::user::User;
use std::str;
type Aes128Cbc = Cbc<Aes128, Pkcs7>;
pub static TOKEN_LIFETIME: i64 = 300; // 5 minutes
lazy_static! {
static ref KEY: [u8; 32] = rand::thread_rng().gen::<[u8; 32]>();
static ref CIPHER: Cipher = Cipher::aes_256_cbc();
static ref IV: [u8; 16] = [0; 16];
static ref CIPHER: Aes128Cbc = Aes128Cbc::new_var(KEY.as_ref(), IV.as_ref()).unwrap();
}
fn text_encrypt(plaintext: &str) -> String {
let iv: &[u8; 16] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let encrypted_vec =
encrypt(*CIPHER, &*KEY, Some(iv), plaintext.as_bytes()).expect("encryption failed");
base64::encode(encrypted_vec.as_slice())
base64::encode(CIPHER.clone().encrypt_vec(plaintext.as_bytes()))
}
fn text_decrypt(ciphertext: &str) -> Option<String> {
let iv: &[u8; 16] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
guard!(let Ok(cipher_vec) = base64::decode(ciphertext) else {
warn!("Unable to decode base64 text");
return None
});
guard!(let Ok(decrypted_vec) = decrypt(*CIPHER, &*KEY, Some(iv), &cipher_vec) else {
guard!(let Ok(decrypted_vec) = CIPHER.clone().decrypt_vec(&cipher_vec) else {
warn!("Text decryption failed");
return None
});
......@@ -76,7 +79,7 @@ pub fn parse_token(discord_user: &User, encrypted_token: &str) -> Result<String,
return Err(TokenError::DiscordIdMismatch);
}
let time_delta_seconds = Utc::now().timestamp() - token_timestamp.timestamp();
if time_delta_seconds > 5 * 60 {
if time_delta_seconds > TOKEN_LIFETIME {
warn!(
"... attempt failed : token expired ({} seconds old)",
time_delta_seconds
......
use rand::Rng;
use regex::Regex;
use serenity::{
model::{channel::Message, guild::Member},
model::{channel::Message, guild::Member, id::RoleId},
prelude::*,
utils::MessageBuilder,
};
use std::process::{Command, Stdio};
use url::Url;
use crate::config::CONFIG;
use crate::database;
use crate::ldap::{ldap_exists, ldap_search};
use crate::token_management::*;
macro_rules! e {
($error: literal, $x:expr) => {
match $x {
Ok(_) => (),
Err(why) => error!($error, why),
}
};
}
pub fn new_member(ctx: &Context, mut new_member: Member) {
pub async fn new_member(ctx: &Context, mut new_member: Member) {
// TODO see if it's an old (registered) user re-joining, and act accordingly
let mut message = MessageBuilder::new();
message.push("Nice to see you here ");
message.mention(&new_member);
message.push_line("! Would you care to introduce yourself?");
message.push_line("If you're not sure where to start, perhaps you could tell us about your projects, your first computer…");
message.push_line("You should also know that we follow the Freenode Channel Guidelines: https://freenode.net/changuide, and try to avoid defamatory content");
if let Err(why) = CONFIG.welcome_channel.say(&ctx, message.build()) {
error!("Error sending message: {:?}", why);
}
message.push_line("You should also know that we follow the Freenode Channel Guidelines: https://freenode.net/changuide, and try to avoid defamatory content.");
message.push("Make sure to check out ");
message.mention(&CONFIG.readme_channel);
message.push(" to get yourself some roles for directed pings 😊, and ");
message.push_mono(format!("{}register username", CONFIG.command_prefix));
message.push(" to link to your UCC account.");
send_message!(CONFIG.welcome_channel, &ctx, message.build());
let mut message = MessageBuilder::new();
message.push(format!("Say hi to {} in ", new_member.display_name()));
message.mention(&CONFIG.welcome_channel);
if let Err(why) = CONFIG.main_channel.say(&ctx, message.build()) {
error!("Error sending message: {:?}", why);
}
send_message!(CONFIG.main_channel, &ctx, message.build());
if let Err(why) = new_member.add_role(&ctx.http, CONFIG.unregistered_member_role) {
if let Err(why) = new_member
.add_role(&ctx.http, CONFIG.unregistered_member_role)
.await
{
error!("Error adding user role: {:?}", why);
};
}
}
fn member_nickname(member: &database::Member) -> String {
let username = member.username.clone();
if let Some(tla) = member.tla.clone() {
if username.to_uppercase() == tla {
return format!("{}", username);
} else {
return format!("{} [{}]", username, tla);
}
} else {
return format!("{}", username);
}
}
pub const RANDOM_SASS: &[&str] = &[
"Please. As if I'd fall for that.",
"Did you really think a stunt like that would work?",
"Nothing slips past me.",
"Did you even read the first line of !help?",
"I never treated you this badly.",
];
pub const RESERVED_NAMES: &[&str] = &[
"committee",
"committee-only",
"ucc",
"ucc-announce",
"tech",
"wheel",
"door",
"coke",
];
pub struct Commands;
impl Commands {
pub fn register(ctx: Context, msg: Message, account_name: &str) {
pub async fn register(ctx: Context, msg: Message, account_name: &str) {
if account_name.is_empty() {
e!(
"Error sending message: {:?}",
msg.channel_id
.say(&ctx.http, "Usage: !register <ucc username>")
send_message!(
msg.channel_id,
&ctx.http,
format!("Usage: {}register <username>", CONFIG.command_prefix)
);
return;
}
if RESERVED_NAMES.contains(&account_name) || database::username_exists(account_name) {
send_message!(
msg.channel_id,
&ctx.http,
RANDOM_SASS[0] //.choose(&mut rand::thread_rng())
//.expect("We couldn't get any sass")
);
return;
}
if !ldap_exists(account_name) {
send_message!(
msg.channel_id,
&ctx.http,
format!(
"I couldn't find an account with the username '{}'",
account_name
)
);
return;
}
send_message!(
msg.channel_id,
&ctx.http,
format!(
"Ok {}, see the email I've just sent you to complete the link",
account_name
)
);
e!(
"Error sending message: {:?}",
// TODO convert to email
msg.channel_id
.say(&ctx.http, format!("Hey {} here's that token you ordered: {}\nIf this wasn't you just ignore this.", account_name, generate_token(&msg.author, account_name)))
"Error deleting register message: {:?}",
msg.delete(ctx).await
);
e!("Error deleting register message: {:?}", msg.delete(ctx));
let message = Command::new("echo").arg(format!("<h3>Link your Discord account</h3>\
<p>Hi {}, to complete the link, go to the discord server and enter\
<pre>{}verify {}</pre>\
</p><sub>The UCC discord bot</sub>",
account_name, CONFIG.command_prefix, generate_token(&msg.author, account_name))).stdout(Stdio::piped()).spawn().expect("Unable to spawn echo command");
match Command::new("mutt")
.arg("-e")
.arg("set content_type=text/html")
.arg("-e")
.arg("set realname=\"UCC Discord Bot\"")
.arg("-s")
.arg("Discord account link token")
.arg(format!("{}@ucc.asn.au", account_name))
.stdin(message.stdout.unwrap())
.output()
{
Ok(_) => info!("Email sent to {}", account_name),
Err(why) => error!("Unable to send message with mutt {:?}", why),
};
}
pub fn get_registered_role(name: String) -> Option<u64> {
guard!(let Some(result) = ldap_search(&name) else {
return None
});
if result.login_shell.contains("locked") && CONFIG.expired_member_role > 0 {
return Some(CONFIG.expired_member_role);
}
Some(CONFIG.registered_member_role)
}
// TODO: make this return a result
// NOTE: don't make this directly send messages, so it can be used for mass updates
pub async fn update_registered_role(ctx: Context, msg: Message) {
guard!(let Ok(member_info) = database::get_member_info(msg.author.id.as_u64()) else {
return // Err()
});
guard!(let Some(registered_role) = Commands::get_registered_role(member_info.username) else {
return // Err()
});
guard!(let Ok(mut discord_member) = serenity::model::id::GuildId(CONFIG.server_id)
.member(ctx.http.clone(), msg.author.id).await else {
return // Err()
});
let roles_to_remove = vec![
CONFIG.registered_member_role,
CONFIG.unregistered_member_role,
CONFIG.expired_member_role,
];
for role in roles_to_remove {
if role == registered_role {
// remove when vec.remove_item is stable
continue;
}
if discord_member.roles.contains(&RoleId::from(role))
&& discord_member.remove_role(&ctx.http, role).await.is_err()
{
return; // Err()
}
}
if !discord_member
.roles
.contains(&RoleId::from(registered_role))
&& discord_member
.add_role(&ctx.http, registered_role)
.await
.is_err()
{
return; // Err()
}
// Ok()
}
pub fn verify(ctx: Context, msg: Message, token: &str) {
pub async fn verify(ctx: Context, msg: Message, token: &str) {
match parse_token(&msg.author, token) {
Ok(name) => {
e!(
"Unable to get member: {:?}",
serenity::model::id::GuildId(CONFIG.server_id)
.member(ctx.http.clone(), msg.author.id)
.map(|mut member| {
e!(
"Unable to remove role: {:?}",
member.remove_role(&ctx.http, CONFIG.unregistered_member_role)
);
e!(
"Unable to edit nickname: {:?}",
member.edit(&ctx.http, |m| {
let mut rng = rand::thread_rng();
m.nickname(format!(
"{}, {}",
name,
[
"The Big Cheese",
"The One and Only",
"The Exalted One",
"not to be trusted",
"The Scoundrel",
"A big fish in a small pond",
][rng.gen_range(0, 5)]
));
m
})
);
let new_msg = msg
.channel_id
.say(&ctx.http, "Verification succesful")
.expect("Error sending message");
e!(
"Error deleting register message: {:?}",
new_msg.delete(&ctx)
);
})
);
if let Ok(mut member) = serenity::model::id::GuildId(CONFIG.server_id)
.member(ctx.http.clone(), msg.author.id)
.await
{
let full_member = database::add_member(&msg.author.id.0, &name);
e!(
"Unable to remove role: {:?}",
member
.remove_role(&ctx.http, CONFIG.unregistered_member_role)
.await
);
guard!(let Some(member_role) = Commands::get_registered_role(name) else {
send_message!(msg.channel_id, ctx.http.clone(), "Couldn't find you in LDAP!");
return
});
e!(
"Unable to add role: {:?}",
member.add_role(&ctx.http, member_role).await
);
e!(
"Unable to edit nickname: {:?}",
member
.edit(&ctx.http, |m| {
m.nickname(member_nickname(&full_member));
m
})
.await
);
let mut verification_message = MessageBuilder::new();
verification_message.push(format!("Great, {}! Verification was successful. To provide a friendly introduction to yourself, consider doing ", &full_member.username));
verification_message
.push_mono(format!("{}set bio <info>", CONFIG.command_prefix));
send_message!(
msg.channel_id,
ctx.http.clone(),
verification_message.build()
);
} else {
error!("Unable to get member: {:?}", name)
}
}
Err(reason) => e!(
"Error sending message: {:?}",
msg.channel_id
.say(&ctx.http, format!("Verification error: {:?}", reason))
Err(reason) => send_message!(
msg.channel_id,
&ctx.http,
format!("Verification error: {:?}", reason)
),
}
e!("Error deleting register message: {:?}", msg.delete(&ctx));
e!(
"Error deleting register message: {:?}",
msg.delete(&ctx).await
);
}
pub async fn profile(ctx: Context, msg: Message, name: &str) {
let possible_member: Option<database::Member> = match if name.trim().is_empty() {
info!(
"{} (discord name) wants to look at their own profile",
&msg.author.name
);
database::get_member_info(&msg.author.id.0)
} else {
info!("Searching for a profile for {}", &name);
database::get_member_info_from_username(&name)
} {
Ok(member) => Some(member),
Err(why) => {
warn!("Could not find member {}, {:?}", &name, why);
if name.len() != 3 {
None
} else {
info!(
"Searching for a profile for the TLA '{}'",
&name.to_uppercase()
);
match database::get_member_info_from_tla(&name.to_uppercase()) {
Ok(member) => Some(member),
Err(_) => None,
}
}
}
};
if possible_member.is_none() {
send_message!(
msg.channel_id,
&ctx.http,
"Sorry, I couldn't find that profile (you need to !register for a profile)"
);
return;
}
let member = possible_member.unwrap();
info!("Found matching profile, UCC username: {}", &member.username);
let user = match ctx.http.get_user(member.discord_id as _).await {
Ok(u) => u,
Err(e) => {
error!("Couldn't find matching Discord ID for username! {:?}", e);
return;
}
};
let result = msg.channel_id.send_message(&ctx.http, |m| {
m.embed(|embed| {
embed.colour(serenity::utils::Colour::LIGHTER_GREY);
embed.footer(|f| {
f.text(&user.name);
f.icon_url(
user.static_avatar_url()
.unwrap_or(String::from("https://www.ucc.asn.au/logos/ucc-logo.png")),
);
f
});
if let Some(name) = member.name.clone() {
embed.title(name);
}
if let Some(photo) = member.photo.clone() {
embed.thumbnail(photo);
}
embed.field("Username", &member.username, true);
if let Some(tla) = member.tla.clone() {
embed.field("TLA", tla, true);
}
if let Some(bio) = member.biography.clone() {
embed.field("Bio", bio, false);
}
if let Some(study) = member.study.clone() {
embed.field("Area of study", study, false);
}
if let Some(git) = member.github.clone() {
embed.field("Git", git, false);
}
if let Some(web) = member.website.clone() {
embed.field("Website", web, false);
}
embed
});
m
});
if let Err(why) = result.await {
error!("Error sending profile embed: {:?}", why);
}
}
pub async fn set_info(ctx: Context, msg: Message, info: &str) {
if info.trim().is_empty() {
msg.channel_id
.send_message(&ctx.http, |m| {
m.embed(|embed| {
embed.colour(serenity::utils::Colour::LIGHT_GREY);
embed.title("Usage");
embed.description(
format!(
"`{}set <field> <info>` or `{}clear <field>`",
CONFIG.command_prefix,
CONFIG.command_prefix,
)
);
embed.field("Biography", format!("`{}set bio <info>`\nBe friendly! Provide a little introduction to yourself.", CONFIG.command_prefix), false);
embed.field("Git", format!("`{}set git <url>`\nA link to your git forge profile. Also takes a github username for convinience", CONFIG.command_prefix), false);
embed.field("Photo", format!("`{}set photo <url>`\nPut a face to a name! Provide a profile photo.", CONFIG.command_prefix), false);
embed.field("Website", format!("`{}set web <info>`\nGot a personal website? Share it here :)", CONFIG.command_prefix), false);
embed.field("Studying", format!("`{}set study <info>`\nYou're (probably) a Uni student, what's your major?", CONFIG.command_prefix), false);
embed
});
m
}).await
.expect("Failed to send usage help embed");
return;
}
let info_content: Vec<_> = info.splitn(2, ' ').collect();
let mut property = String::from(info_content[0]);
property = property.replace("github", "git");
if info_content.len() == 1
|| !vec!["bio", "git", "web", "photo", "study"].contains(&property.as_str())
{
msg.channel_id
.send_message(&ctx.http, |m| {
m.embed(|embed| {
embed.colour(serenity::utils::Colour::LIGHT_GREY);
embed.title("Usage");
embed.field(
match property.as_str() {
"bio" => "Biography",
"git" => "Git Forge Profile",
"photo" => "Profile Photo",
"web" => "Personal Website",
"study" => "Area of study",
_ => "???",
},
format!(
"`{}set {} <info>` or `{}clear {}`\n{}",
CONFIG.command_prefix,
property,
CONFIG.command_prefix,
property,
match property.as_str() {
"bio" => "Some information about yourself :)",
"git" => "A url to your git{hub,lab} account",
"photo" => "A url to a profile photo online",
"web" => "A url to your website/webpage",
"study" => "Your degree title",
_ => "Whatever you want, because this does absolutely nothing.",
}
),
false,
);
embed
});
m
})
.await
.expect("Failed to send usage embed");
return;
}
let mut value = info_content[1].to_string();
if vec!["git", "photo", "web"].contains(&property.as_str()) {
if Url::parse(&value).is_err() {
let user_regex = Regex::new(r"^\w+$").unwrap();
if property == "git" && user_regex.is_match(&value) {
value = format!("github.com/{}", value);
}
value = format!("https://{}", value);
if Url::parse(&value).is_err() {
send_message!(
msg.channel_id,
&ctx.http,
"That ain't a URL where I come from..."
);
return;
}
}
}
guard!(let Ok(member) = database::get_member_info(msg.author.id.as_u64()) else {
send_message!(
msg.channel_id,
&ctx.http,
format!(
"You don't seem to have a profile. {}register to get one",
CONFIG.command_prefix
)
);
return
});
let set_property = match property.as_str() {
"bio" => database::set_member_bio(&msg.author.id.0, Some(&value)),
"git" => database::set_member_git(&msg.author.id.0, Some(&value)),
"photo" => database::set_member_photo(&msg.author.id.0, Some(&value)),
"web" => database::set_member_website(&msg.author.id.0, Some(&value)),
"study" => database::set_member_study(&msg.author.id.0, Some(&value)),
_ => Err(diesel::result::Error::NotFound),
};
match set_property {
Ok(_) => {
info!(
"Set {}'s {} in profile to {}",
&msg.author_nick(ctx.http.clone())
.await
.unwrap_or(String::from("?")),
property,
value
);
if property == "git" && member.photo == None {
let git_url = Url::parse(&value).unwrap(); // we parsed this earlier and it was fine
match git_url.host_str() {
Some("github.com") => {
if let Some(mut path_segments) = git_url.path_segments() {
database::set_member_photo(
&msg.author.id.0,
Some(
format!(
"https://github.com/{}.png",
path_segments.next().expect("URL doesn't have a path")
)
.as_str(),
),
)
.expect("Attempt to set member photo failed");
info!(" ... and set profile photo to github photo")
}
}
_ => {}
}
}
}
Err(why) => {
error!(
"Umable to set property {} to {} in DB {:?}",
property, value, why
);
send_message!(msg.channel_id, &ctx.http, "Failed to set property. Ooops.");
}
}
if let Err(why) = msg.delete(&ctx).await {
error!("Error deleting set profile property: {:?}", why);
}
}
pub async fn clear_info(ctx: Context, msg: Message, field: &str) {
if field.trim().is_empty() {
// just show the help page from set_info
return Commands::set_info(ctx, msg, "").await;
}
match field {
"bio" => database::set_member_bio(&msg.author.id.0, None),
"git" => database::set_member_git(&msg.author.id.0, None),
"photo" => database::set_member_photo(&msg.author.id.0, None),
"web" => database::set_member_website(&msg.author.id.0, None),
"study" => database::set_member_study(&msg.author.id.0, None),
_ => Err(diesel::result::Error::NotFound),
}
.expect("Unable to clear profile field");
info!(
"Cleared {}'s {} in profile",
&msg.author_nick(ctx.http).await.unwrap_or(String::from("?")),
field,
);
}
}
use serenity::model::{channel::ReactionType, guild::PartialGuild};
use std::str::FromStr;
use serenity::model::{channel::ReactionType, guild::PartialGuild, misc::EmojiIdentifier};
pub fn get_string_from_react(react: &ReactionType) -> String {
match react {
......@@ -17,7 +19,46 @@ pub fn get_react_from_string(string: String, guild: PartialGuild) -> ReactionTyp
.values()
.find(|e| e.name == string)
.map_or_else(
|| ReactionType::from(string), // unicode emoji
|| {
ReactionType::from(EmojiIdentifier::from_str(&string).expect(&format!(
"Emoji string \"{}\" could not be identified",
&string
)))
}, // unicode emoji
|custom_emoji| ReactionType::from(custom_emoji.clone()),
)
}
#[macro_use]
macro_rules! e {
($error: literal, $x:expr) => {
match $x {
Ok(_) => (),
Err(why) => error!($error, why),
}
};
}
#[macro_use]
macro_rules! send_message {
($chan:expr, $context:expr, $message:expr) => {
match $chan.say($context, $message).await {
Ok(_) => (),
Err(why) => error!("Error sending message: {:?}", why),
}
};
}
#[allow(unused_macros)] // remove this if you start using it
#[macro_use]
macro_rules! send_delete_message {
($chan:expr, $context:expr, $message:expr) => {
match $chan.say($context, $message).await {
Ok(the_new_msg) => e!(
"Error deleting register message: {:?}",
the_new_msg.delete($context)
),
Err(why) => error!("Error sending message: {:?}", why),
}
};
}
use serenity::{
model::{channel, channel::Message},
model::{channel, channel::Message, misc::EmojiIdentifier},
prelude::*,
utils::MessageBuilder,
};
use std::collections::HashMap;
use std::sync::Mutex;
use std::str::FromStr;
use tokio::sync::Mutex;
use crate::config::CONFIG;
use crate::util::get_string_from_react;
macro_rules! e {
($error: literal, $x:expr) => {
match $x {
Ok(_) => (),
Err(why) => error!($error, why),
}
};
}
pub struct Commands;
impl Commands {
pub fn move_something(ctx: Context, msg: Message, content: &str) {
pub async fn move_something(ctx: Context, msg: Message, content: &str) {
let motion = content;
if !motion.is_empty() {
create_motion(&ctx, &msg, motion);
return;
return create_motion(&ctx, &msg, motion).await;
}
e!(
"Error sending message: {:?}",
msg.channel_id.say(
&ctx.http,
"If there's something you want to motion, put it after the !move keyword",
)
send_message!(
msg.channel_id,
&ctx.http,
"If there's something you want to motion, put it after the !move keyword"
);
}
pub fn motion(ctx: Context, msg: Message, _content: &str) {
e!("Error sending message: {:?}",
msg.channel_id.say(
&ctx.http,
"I hope you're not having a motion. You may have wanted to !move something instead."
));
pub async fn motion(ctx: Context, msg: Message, _content: &str) {
send_message!(
msg.channel_id,
&ctx.http,
"I hope you're not having a motion. You may have wanted to !move something instead."
);
}
pub fn poll(ctx: Context, msg: Message, content: &str) {
pub async fn poll(ctx: Context, msg: Message, content: &str) {
let topic = content;
if !topic.is_empty() {
create_poll(&ctx, &msg, topic);
return;
return create_poll(&ctx, &msg, topic).await;
}
e!(
"Error sending message: {:?}",
msg.channel_id.say(
&ctx.http,
"If there's something you want to motion, put it after the !move keyword",
)
send_message!(
msg.channel_id,
&ctx.http,
"If there's something you want to motion, put it after the !move keyword"
);
}
pub fn cowsay(ctx: Context, msg: Message, content: &str) {
pub async fn cowsay(ctx: Context, msg: Message, content: &str) {
let output = if !content.trim().is_empty() {
let mut text = content.to_owned();
text.escape_default();
......@@ -69,25 +55,22 @@ impl Commands {
} else {
std::process::Command::new("sh")
.arg("-c")
.arg("sh -c fortune | cowsay -f \"/usr/share/cowsay/cows/$(echo 'www\nhellokitty\nbud-frogs\nkoala\nsuse\nthree-eyes\npony-smaller\nsheep\nvader\ncower\nmoofasa\nelephant\nflaming-sheep\nskeleton\nsnowman\ntux\napt\nmoose' | shuf -n 1).cow\"")
.arg("fortune | cowsay -f \"/usr/share/cowsay/cows/$(echo 'www\nhellokitty\nbud-frogs\nkoala\nsuse\nthree-eyes\npony-smaller\nsheep\nvader\ncower\nmoofasa\nelephant\nflaming-sheep\nskeleton\nsnowman\ntux\napt\nmoose' | shuf -n 1).cow\"")
.output()
.expect("failed to execute fortune/cowsay")
};
let mut message = MessageBuilder::new();
message.push_codeblock(
message.push_codeblock_safe(
String::from_utf8(output.stdout).expect("unable to parse stdout to String"),
None,
);
e!(
"Error sending message: {:?}",
msg.channel_id.say(&ctx.http, message.build())
);
send_message!(msg.channel_id, &ctx.http, message.build());
}
}
fn create_motion(ctx: &Context, msg: &Message, topic: &str) {
async fn create_motion(ctx: &Context, msg: &Message, topic: &str) {
info!("{} created a motion {}", msg.author.name, topic);
if let Err(why) = msg.delete(ctx) {
if let Err(why) = msg.delete(ctx).await {
error!("Error deleting motion prompt: {:?}", why);
}
let result = msg.channel_id.send_message(&ctx.http, |m| {
......@@ -114,53 +97,57 @@ fn create_motion(ctx: &Context, msg: &Message, topic: &str) {
embed
});
m.reactions(vec![
CONFIG.for_vote.to_string(),
CONFIG.against_vote.to_string(),
CONFIG.abstain_vote.to_string(),
CONFIG.approve_react.to_string(),
CONFIG.disapprove_react.to_string(),
EmojiIdentifier::from_str(&CONFIG.for_vote).unwrap(),
EmojiIdentifier::from_str(&CONFIG.against_vote).unwrap(),
EmojiIdentifier::from_str(&CONFIG.abstain_vote).unwrap(),
EmojiIdentifier::from_str(&CONFIG.approve_react).unwrap(),
EmojiIdentifier::from_str(&CONFIG.disapprove_react).unwrap(),
]);
m
});
if let Err(why) = result {
if let Err(why) = result.await {
error!("Error creating motion: {:?}", why);
}
}
fn create_poll(ctx: &Context, msg: &Message, topic: &str) {
async fn create_poll(ctx: &Context, msg: &Message, topic: &str) {
info!("{} created a poll {}", msg.author.name, topic);
match msg.channel_id.send_message(&ctx.http, |m| {
m.embed(|embed| {
embed.author(|a| {
a.name(&msg.author.name);
a.icon_url(
msg.author
.static_avatar_url()
.expect("Expected author to have avatar"),
);
a
match msg
.channel_id
.send_message(&ctx.http, |m| {
m.embed(|embed| {
embed.author(|a| {
a.name(&msg.author.name);
a.icon_url(
msg.author
.static_avatar_url()
.expect("Expected author to have avatar"),
);
a
});
embed.colour(serenity::utils::Colour::BLUE);
embed.title(format!("Poll {}", topic));
let mut desc = MessageBuilder::new();
desc.mention(&msg.author);
desc.push(" wants to know what you think.");
embed.description(desc.build());
embed.timestamp(msg.timestamp.to_rfc3339());
embed
});
embed.colour(serenity::utils::Colour::BLUE);
embed.title(format!("Poll {}", topic));
let mut desc = MessageBuilder::new();
desc.mention(&msg.author);
desc.push(" wants to know what you think.");
embed.description(desc.build());
embed.timestamp(msg.timestamp.to_rfc3339());
embed
});
m.reactions(vec![
CONFIG.approve_react.to_string(),
CONFIG.disapprove_react.to_string(),
CONFIG.unsure_react.to_string(),
]);
m
}) {
m.reactions(vec![
EmojiIdentifier::from_str(&CONFIG.approve_react).unwrap(),
EmojiIdentifier::from_str(&CONFIG.disapprove_react).unwrap(),
EmojiIdentifier::from_str(&CONFIG.unsure_react).unwrap(),
]);
m
})
.await
{
Err(why) => {
error!("Error sending message: {:?}", why);
}
Ok(_) => {
if let Err(why) = msg.delete(ctx) {
if let Err(why) = msg.delete(ctx).await {
error!("Error deleting motion prompt: {:?}", why);
}
}
......@@ -177,28 +164,43 @@ lazy_static! {
Mutex::new(HashMap::new());
}
fn get_cached_motion(ctx: &Context, msg: &Message) -> MotionInfo {
let mut cached_motions = MOTIONS_CACHE.lock().unwrap();
async fn get_cached_motion(ctx: &Context, msg: &Message) -> MotionInfo {
let mut cached_motions = MOTIONS_CACHE.lock().await;
if !cached_motions.contains_key(&msg.id) {
info!("Initialising representation of motion {:?}", msg.id);
let for_votes = msg
.reaction_users(
ctx,
EmojiIdentifier::from_str(&CONFIG.for_vote).unwrap(),
Some(100),
None,
)
.await
.unwrap();
let against_votes = msg
.reaction_users(
ctx,
EmojiIdentifier::from_str(&CONFIG.against_vote).unwrap(),
Some(100),
None,
)
.await
.unwrap();
let abstain_votes = msg
.reaction_users(
ctx,
EmojiIdentifier::from_str(&CONFIG.abstain_vote).unwrap(),
Some(100),
None,
)
.await
.unwrap();
let this_motion = MotionInfo {
votes: {
let mut m = HashMap::new();
m.insert(
CONFIG.for_vote.to_string(),
msg.reaction_users(ctx, CONFIG.for_vote.to_string(), Some(100), None)
.unwrap(),
);
m.insert(
CONFIG.against_vote.to_string(),
msg.reaction_users(ctx, CONFIG.against_vote.to_string(), Some(100), None)
.unwrap(),
);
m.insert(
CONFIG.abstain_vote.to_string(),
msg.reaction_users(ctx, CONFIG.abstain_vote.to_string(), Some(100), None)
.unwrap(),
);
m.insert(CONFIG.for_vote.to_string(), for_votes);
m.insert(CONFIG.against_vote.to_string(), against_votes);
m.insert(CONFIG.abstain_vote.to_string(), abstain_votes);
m
},
};
......@@ -206,52 +208,51 @@ fn get_cached_motion(ctx: &Context, msg: &Message) -> MotionInfo {
}
(*cached_motions.get(&msg.id).unwrap()).clone()
}
fn set_cached_motion(id: serenity::model::id::MessageId, motion_info: MotionInfo) {
if let Some(motion) = MOTIONS_CACHE.lock().unwrap().get_mut(&id) {
async fn set_cached_motion(id: serenity::model::id::MessageId, motion_info: MotionInfo) {
if let Some(motion) = MOTIONS_CACHE.lock().await.get_mut(&id) {
*motion = motion_info;
return;
}
warn!("{}", "Couldn't find motion in cache to set");
}
fn update_motion(
async fn update_motion(
ctx: &Context,
msg: &mut Message,
user: &serenity::model::user::User,
change: &str,
reaction: channel::Reaction,
) {
let motion_info: MotionInfo = get_cached_motion(ctx, msg);
let motion_info: MotionInfo = get_cached_motion(ctx, msg).await;
async fn tiebreaker(ctx: &Context, motion: &MotionInfo, vote_type: &str) -> f32 {
if let Some(votes) = motion.votes.get(vote_type) {
for voter in votes {
match voter
.has_role(ctx, CONFIG.server_id, CONFIG.tiebreaker_role)
.await
{
Ok(true) => return 0.25,
_ => continue,
}
}
0.0
} else {
warn!("Couldn't find \"{}\" vote for {:?}", vote_type, motion);
0.0
}
}
let for_votes = motion_info.votes.get(&CONFIG.for_vote).unwrap().len() as isize - 1;
let against_votes = motion_info.votes.get(&CONFIG.against_vote).unwrap().len() as isize - 1;
let abstain_votes = motion_info.votes.get(&CONFIG.abstain_vote).unwrap().len() as isize - 1;
let has_tiebreaker = |users: &Vec<serenity::model::user::User>| {
users.iter().any(|u| {
u.has_role(ctx, CONFIG.server_id, CONFIG.tiebreaker_role)
.unwrap()
})
};
let for_strength = for_votes as f32
+ (if has_tiebreaker(motion_info.votes.get(&CONFIG.for_vote).unwrap()) {
0.25
} else {
0.0
});
let against_strength = against_votes as f32
+ (if has_tiebreaker(motion_info.votes.get(&CONFIG.against_vote).unwrap()) {
0.25
} else {
0.0
});
let abstain_strength = abstain_votes as f32
+ (if has_tiebreaker(motion_info.votes.get(&CONFIG.abstain_vote).unwrap()) {
0.25
} else {
0.0
});
let for_strength = for_votes as f32 + tiebreaker(ctx, &motion_info, &CONFIG.for_vote).await;
let against_strength =
against_votes as f32 + tiebreaker(ctx, &motion_info, &CONFIG.against_vote).await;
let abstain_strength =
abstain_votes as f32 + tiebreaker(ctx, &motion_info, &CONFIG.abstain_vote).await;
let old_embed = msg.embeds[0].clone();
let topic = old_embed.clone().title.unwrap();
......@@ -264,10 +265,13 @@ fn update_motion(
topic
);
let update_status = |e: &mut serenity::builder::CreateEmbed,
status: &str,
last_status_full: String,
topic: &str| {
fn update_status(
ctx: &Context,
e: &mut serenity::builder::CreateEmbed,
status: &str,
last_status_full: String,
topic: &str,
) {
let last_status = last_status_full.lines().next().expect("No previous status");
if last_status == status {
e.field("Status", last_status_full, true);
......@@ -284,75 +288,86 @@ fn update_motion(
message.push(" is now ");
message.push_bold(status);
message.push_italic(format!(" (was {})", last_status));
if let Err(why) = CONFIG.announcement_channel.say(&ctx.http, message.build()) {
error!("Error sending message: {:?}", why);
};
let ctx = ctx.clone();
tokio::spawn(async move {
CONFIG
.announcement_channel
.say(ctx.http, message.build())
.await
});
}
};
}
if let Err(why) = msg.edit(ctx, |m| {
m.embed(|e| {
e.author(|a| {
let old_author = old_embed.clone().author.expect("Expected author in embed");
a.name(old_author.name);
a.icon_url(
old_author
.icon_url
.expect("Expected embed author to have icon"),
if let Err(why) = msg
.edit(ctx, |m| {
m.embed(|e| {
e.author(|a| {
let old_author = old_embed.clone().author.expect("Expected author in embed");
a.name(old_author.name);
a.icon_url(
old_author
.icon_url
.expect("Expected embed author to have icon"),
);
a
});
e.title(&topic);
e.description(old_embed.description.unwrap());
let last_status_full = old_embed
.fields
.iter()
.find(|f| f.name == "Status")
.expect("No previous status")
.clone()
.value;
if for_strength > (CONFIG.vote_pool_size as f32 / 2.0) {
e.colour(serenity::utils::Colour::TEAL);
update_status(ctx, e, "Passed", last_status_full, &topic);
} else if against_strength + abstain_strength > (CONFIG.vote_pool_size as f32 / 2.0)
{
e.colour(serenity::utils::Colour::RED);
update_status(ctx, e, "Failed", last_status_full, &topic);
} else {
e.colour(serenity::utils::Colour::GOLD);
update_status(ctx, e, "Under Consideration", last_status_full, &topic);
}
e.field(
format!(
"Votes ({}/{})",
for_votes + against_votes + abstain_votes,
CONFIG.vote_pool_size
),
format!(
"For: {}\nAgainst: {}\nAbstain: {}",
for_votes, against_votes, abstain_votes
),
true,
);
a
});
e.title(&topic);
e.description(old_embed.description.unwrap());
let last_status_full = old_embed
.fields
.iter()
.find(|f| f.name == "Status")
.expect("No previous status")
.clone()
.value;
if for_strength > (CONFIG.vote_pool_size as f32 / 2.0) {
e.colour(serenity::utils::Colour::TEAL);
update_status(e, "Passed", last_status_full, &topic);
} else if against_strength + abstain_strength > (CONFIG.vote_pool_size as f32 / 2.0) {
e.colour(serenity::utils::Colour::RED);
update_status(e, "Failed", last_status_full, &topic);
} else {
e.colour(serenity::utils::Colour::GOLD);
update_status(e, "Under Consideration", last_status_full, &topic);
}
e.field(
format!(
"Votes ({}/{})",
for_votes + against_votes + abstain_votes,
CONFIG.vote_pool_size
),
format!(
"For: {}\nAgainst: {}\nAbstain: {}",
for_votes, against_votes, abstain_votes
),
true,
);
e.timestamp(
old_embed
.timestamp
.expect("Expected embed to have timestamp"),
);
e
e.timestamp(
old_embed
.timestamp
.expect("Expected embed to have timestamp"),
);
e
})
})
}) {
.await
{
error!("Error updating motion: {:?}", why);
}
}
pub fn reaction_add(ctx: Context, add_reaction: channel::Reaction) {
pub async fn reaction_add(ctx: &Context, add_reaction: channel::Reaction) {
let react_as_string = get_string_from_react(&add_reaction.emoji);
match add_reaction.message(&ctx.http) {
match add_reaction.message(&ctx.http).await {
Ok(mut message) => {
guard!(let Ok(user) = add_reaction.user(&ctx) else {
guard!(let Ok(user) = add_reaction.user(&ctx).await else {
return
});
match user.has_role(&ctx, CONFIG.server_id, CONFIG.vote_role) {
match user
.has_role(&ctx, CONFIG.server_id, CONFIG.vote_role)
.await
{
Ok(true) => {
// remove vote if already voted
for react in [
......@@ -364,11 +379,18 @@ pub fn reaction_add(ctx: Context, add_reaction: channel::Reaction) {
.filter(|r| r != &&react_as_string)
{
for a_user in message
.reaction_users(&ctx, react.as_str(), None, None)
.reaction_users(
&ctx,
EmojiIdentifier::from_str(react.as_str()).unwrap(),
None,
None,
)
.await
.ok()
.unwrap()
{
if a_user.id.0 == user.id.0 {
if let Err(why) = add_reaction.delete(&ctx) {
if a_user == user {
if let Err(why) = add_reaction.delete(&ctx).await {
error!("Error deleting react: {:?}", why);
};
return;
......@@ -377,19 +399,19 @@ pub fn reaction_add(ctx: Context, add_reaction: channel::Reaction) {
}
// remove 'illegal' reacts
if !CONFIG.allowed_reacts().contains(&react_as_string) {
if let Err(why) = add_reaction.delete(&ctx) {
if let Err(why) = add_reaction.delete(&ctx).await {
error!("Error deleting react: {:?}", why);
};
return;
}
// update motion
let mut motion_info = get_cached_motion(&ctx, &message);
let mut motion_info = get_cached_motion(&ctx, &message).await;
if let Some(vote) = motion_info.votes.get_mut(&react_as_string) {
vote.retain(|u| u.id != user.id);
vote.push(user.clone());
}
set_cached_motion(message.id, motion_info);
update_motion(&ctx, &mut message, &user, "add", add_reaction);
set_cached_motion(message.id, motion_info).await;
update_motion(&ctx, &mut message, &user, "add", add_reaction).await
}
Ok(false) => {
if ![
......@@ -398,7 +420,7 @@ pub fn reaction_add(ctx: Context, add_reaction: channel::Reaction) {
]
.contains(&react_as_string)
{
if let Err(why) = add_reaction.delete(&ctx) {
if let Err(why) = add_reaction.delete(&ctx).await {
error!("Error deleting react: {:?}", why);
};
return;
......@@ -415,19 +437,19 @@ pub fn reaction_add(ctx: Context, add_reaction: channel::Reaction) {
}
}
pub fn reaction_remove(ctx: Context, removed_reaction: channel::Reaction) {
match removed_reaction.message(&ctx.http) {
pub async fn reaction_remove(ctx: &Context, removed_reaction: channel::Reaction) {
match removed_reaction.message(&ctx.http).await {
Ok(mut message) => {
if let Ok(user) = removed_reaction.user(&ctx) {
let mut motion_info = get_cached_motion(&ctx, &message);
if let Ok(user) = removed_reaction.user(&ctx).await {
let mut motion_info = get_cached_motion(&ctx, &message).await;
if let Some(vote) = motion_info
.votes
.get_mut(&get_string_from_react(&removed_reaction.emoji))
{
vote.retain(|u| u.id != user.id);
}
set_cached_motion(message.id, motion_info);
update_motion(&ctx, &mut message, &user, "remove", removed_reaction);
set_cached_motion(message.id, motion_info).await;
update_motion(&ctx, &mut message, &user, "remove", removed_reaction).await;
}
}
Err(why) => {
......