diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b55109 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +config.ron \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d3f7fa6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1399 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "async-trait" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-tungstenite" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" +dependencies = [ + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "tokio", + "tokio-rustls", + "tungstenite", + "webpki-roots", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[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", + "winapi", +] + +[[package]] +name = "command_attr" +version = "0.4.0" +source = "git+https://github.com/serenity-rs/serenity?branch=current#5eb6118ff7dfe0d801dab43225a5fedad9dfb913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dashmap" +version = "5.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f" +dependencies = [ + "cfg-if", + "hashbrown 0.12.1", + "lock_api", + "parking_lot_core", + "serde", +] + +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "flate2" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af" +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 = "form_urlencoded" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" +dependencies = [ + "matches", + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-sink" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", +] + +[[package]] +name = "h2" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util 0.7.2", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "http" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "496ce29bb5a52785b44e0f7ca2847ae0bb839c9bd28f69acac9b99d461c0c04c" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +dependencies = [ + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +dependencies = [ + "autocfg", + "hashbrown 0.11.2", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "js-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +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 = "levenshtein" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713d550d9b44d89174e066b7a6217ae06234c10cb47819a88290d2b353c31799" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_threads" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "ordered-float" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" +dependencies = [ + "num-traits", +] + +[[package]] +name = "parking_lot" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "proc-macro2" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "project-dc-bot" +version = "0.1.0" +dependencies = [ + "ron", + "serde", + "serenity", + "tokio", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "reqwest" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-rustls", + "ipnet", + "js-sys", + "lazy_static", + "log", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-rustls", + "tokio-util 0.6.10", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "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 = "ron" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b861ecaade43ac97886a512b360d01d66be9f41f3c61088b42cedf92e03d678" +dependencies = [ + "base64", + "bitflags", + "serde", +] + +[[package]] +name = "rustls" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" +dependencies = [ + "base64", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serenity" +version = "0.11.2" +source = "git+https://github.com/serenity-rs/serenity?branch=current#5eb6118ff7dfe0d801dab43225a5fedad9dfb913" +dependencies = [ + "async-trait", + "async-tungstenite", + "base64", + "bitflags", + "bytes", + "cfg-if", + "chrono", + "command_attr", + "dashmap", + "flate2", + "futures", + "levenshtein", + "mime", + "mime_guess", + "parking_lot", + "percent-encoding", + "reqwest", + "serde", + "serde-value", + "serde_json", + "static_assertions", + "time", + "tokio", + "tracing", + "typemap_rev", + "url", + "uwl", +] + +[[package]] +name = "sha-1" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "socket2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +dependencies = [ + "itoa", + "libc", + "num_threads", + "serde", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +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.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" +dependencies = [ + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "once_cell", + "pin-project-lite", + "socket2", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[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.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +dependencies = [ + "lazy_static", +] + +[[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.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" +dependencies = [ + "base64", + "byteorder", + "bytes", + "http", + "httparse", + "log", + "rand", + "rustls", + "sha-1", + "thiserror", + "url", + "utf-8", + "webpki", +] + +[[package]] +name = "typemap_rev" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" +dependencies = [ + "form_urlencoded", + "idna", + "matches", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uwl" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[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.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + +[[package]] +name = "web-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" +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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "winreg" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +dependencies = [ + "winapi", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..8bf4b1c --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "project-dc-bot" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ron = "0.7.0" +serde = { version = "1.0.130", features = ["derive"] } +serde_json = "1.0.81" +bracket-random = "0.8.2" +reqwest = { version = "0.11", features = ["blocking", "json"] } +string-builder = "0.2.0" +const_format = "0.2.5" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +[dependencies.serenity] +git = "https://github.com/serenity-rs/serenity" +branch = "current" +features = [ + "builder", + "cache", + "framework", + "model", + "standard_framework", + "rustls_backend", + "utils", + "client", + "gateway" +] diff --git a/README.md b/README.md index 7c2efa5..56f0e3d 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,64 @@ -# Frobnicator +# Discord Game Bot -## Autorzy -- Andrzej Głuszak (gr 9, @agluszak na githubie) -- Linus Torvalds (Uniwersytet Helsiński, @torvalds na githubie) +## Autors +- Katarzyna Mielnik @mielnikk +- Julia Podrażka @julia-podrazka -## Opis -Od zawsze chcieliśmy napisać grę komputerową. -Frobnicator będzie to gra platformowa, w której chodzi o to, żeby... +## Description +Discord bot for playing Wordle. -Z grubsza będziemy wzorować się na [tym tutorialu](https://dev.to/sbelzile/rust-platformer-part-1-bevy-and-ecs-2pci). +Each user can start a game to play alone or in a group. -## Funkcjonalność -- Generowanie map -- Strzelanie -- AI dla wrogów (bardziej rozbudowane niż w tutorialu) -- Możliwość zapisywania i wczytywania stanu gry -- Punktacja +Each channel can have multiple solo games at a time but only one multiplayer game. -## Propozycja podziału na części -W pierwszej części stworzymy grę opartą na tutorialu (z lepszym AI) i jedną zahardcodowaną planszą. +There are only 5-letter words. -W drugiej części dodamy do tego losowy generator map, zapisywanie/wczytywanie stanu gry oraz system punktacji. +Each game has a timer - you only have 5 minutes to guess the word, and you are given a maximum of 6 guesses. -## Biblioteki -- Bevy -- może coś do serializacji danych? (czy mógłby Pan coś polecić?) +## Installing a bot +First, put your bot's token in the correct struct in `config.rs`. + +Then run command: +``` +cargo run +``` + +## How to play +To start a solo game enter: +``` +!start +``` + +To start a game with your friends enter: +``` +!start +``` +Now, each of your friends can enter: +``` +!join +``` +to play with you. + +To guess, enter: +``` +!guess +``` + +After each guess, the color of the letters will change to show how close your guess was to the word. +If the letter is green, it is in the word and in the correct spot. +If the letter is yellow, it is in the word but in the wrong spot. +If the letter is red, it is not in the word in any spot. + +If you want to give up a game, click on a white flag in reactions under your last guess or enter +``` +!giveup +``` +Then the game will be finished, and you will see a correct word with its definition. + +To see the rules in the game enter: +``` +!help +``` + +## Libraries +Our program uses primarily Serenity, as well as Tokio and Serde. diff --git a/config.ron b/config.ron new file mode 100644 index 0000000..d246646 --- /dev/null +++ b/config.ron @@ -0,0 +1,4 @@ +( + token: "Your token goes here", + prefix: "!", +) \ No newline at end of file diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..ecfb927 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,34 @@ + +use ron::{de}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Config { + token: String, + prefix: String, +} + +impl Config { + pub fn token(&self) -> &str { + self.token.as_str() + } + + pub fn prefix(&self) -> &str { + self.prefix.as_str() + } + + /* Deserializes the configuration data from 'config.ron' and initializes app's settings. */ + pub fn load() -> std::io::Result { + let input_path = format!("{}/config.ron", env!("CARGO_MANIFEST_DIR")); + let f = std::fs::File::open(&input_path).expect("Failed opening file"); + let config: Config = match de::from_reader(&f) { + Ok(x) => x, + Err(e) => { + println!("Failed to load config: {}", e); + std::process::exit(1); + } + }; + + Ok(config) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0f6116c --- /dev/null +++ b/src/main.rs @@ -0,0 +1,421 @@ +mod config; +mod messages; +mod wordle; +mod words; + +use crate::messages::*; +use crate::wordle::{DEFAULT_SIZE, GUESSES}; +use crate::words::Words; +use config::Config; +use serenity::futures::TryFutureExt; + +use serenity::{ + async_trait, + client::ClientBuilder, + framework::standard::{macros::command, macros::group, Args, CommandResult, StandardFramework}, + model::id::*, + model::prelude::*, + prelude::*, +}; +use std::collections::HashMap; +use std::sync::Arc; +use std::time::SystemTime; +use std::vec::Vec; +use string_builder::Builder; +use tokio::sync::{Mutex, MutexGuard}; +use wordle::Wordle; + +/* Every solo player has 5 minutes to complete game. + * Group players have 5 minutes to join a game and another 5 minutes to play. */ +pub const GAME_TIME: u64 = 5 * 60; + +/* Structure to share data across server. */ +struct ServerKey; + +impl TypeMapKey for ServerKey { + type Value = Arc>; +} + +/* Contains information on all instances of Wordle that have been started, + * max people playing, vector of people that joined group play and all available words to guess. */ +struct ServerMap { + games: HashMap<(ChannelId, UserId), (Wordle, SystemTime)>, + /* Takes value: one if there is at least one solo play or + * max number of players in a group if there is a group play. */ + max_people_playing: usize, + joined_people: Vec, + words: Words, +} + +impl ServerMap { + pub async fn new() -> ServerMap { + ServerMap { + games: HashMap::new(), + max_people_playing: 1, + joined_people: Vec::new(), + words: Words::new().await, + } + } +} + +/* Creates a vector of user ids of all people that have joined a game instance. + * If there's no group game going on, appends wordle_starter as the only player. */ +fn get_players(wordle_map: &mut MutexGuard<'_, ServerMap>, wordle_starter: UserId) -> Vec { + if wordle_map.max_people_playing > 1 { + wordle_map.joined_people.clone() + } else { + vec![wordle_starter] + } +} + +fn clean_joined_and_max_playing(wordle_map: &mut MutexGuard<'_, ServerMap>) { + wordle_map.joined_people.clear(); + wordle_map.max_people_playing = 1; +} + +struct Handler; + +#[async_trait] +impl EventHandler for Handler { + /* The bot's response to adding a reaction to a message. + * It checks whether a user playing Wordle has reacted with a white flag emoji, + * meaning they have given up on guessing. The bot ends their game. */ + async fn reaction_add(&self, _ctx: Context, _add_reaction: Reaction) { + let user = if let Some(u) = _add_reaction.user_id { + u + } else { + return; + }; + if !_add_reaction.emoji.unicode_eq("🏳") { + return; + } + let mut wordle_data = _ctx.data.write().await; + let mut wordle_map = wordle_data + .get_mut::() + .expect("Failed to retrieve wordle map!") + .lock() + .await; + /* Indicates whether the game is played by one person or a group.*/ + let single_player = wordle_map.max_people_playing == 1; + + /* Ignoring if a non-player reacts to the message. */ + if !single_player && !wordle_map.joined_people.contains(&user) { + return; + } + let players = get_players(&mut wordle_map, user); + /* Finding a game the reaction was added to. + * The reaction must be added to the latest wordle display of the game, + * otherwise the bot will not respond. */ + let mut coll = wordle_map + .games + .iter_mut() + .filter(|(_, (w, _))| w.last_message_id == Some(_add_reaction.message_id)); + if let Some((&(_, player), (wordle, _))) = coll.next() { + /* Somebody else reacted to a player's game. */ + if single_player && player != user { + return; + } + send_wordle_solution(wordle, &_add_reaction.channel_id, players, &_ctx.http).await; + /* Removing information about the instance. */ + wordle_map + .games + .retain(|_, (w, _)| w.last_message_id != Some(_add_reaction.message_id)); + clean_joined_and_max_playing(&mut wordle_map); + } + } +} + +#[command] +async fn help(ctx: &Context, msg: &Message) -> CommandResult { + send_embed_message(ctx, msg, HELP_MSG).await +} + +/* Removes all games that took longer than 5 minutes to play/gather enough players. */ +fn check_ended_games(wordle_map: &mut MutexGuard<'_, ServerMap>) { + wordle_map + .games + .retain(|_, time| time.1.elapsed().expect("Failed to get time!").as_secs() < GAME_TIME); +} + +async fn add_new_wordle(msg: &Message, wordle_map: &mut Arc>) { + let wordle = Wordle::new(wordle_map.lock().await.words.generate_word().word.clone()); + wordle_map + .lock() + .await + .games + .insert((msg.channel_id, msg.author.id), (wordle, SystemTime::now())); +} + +#[command] +async fn start(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { + /* Gets shared data across whole server. */ + let mut wordle_data = ctx.data.write().await; + let wordle_map = wordle_data + .get_mut::() + .expect("Failed to retrieve wordle map!"); + + check_ended_games(&mut wordle_map.lock().await); + + /* No one can start a game if a group is playing/gathering players. */ + if wordle_map.lock().await.max_people_playing > 1 { + return send_embed_message(ctx, msg, GROUP_PLAYING_MSG).await; + } + + /* Starting game for solo player. */ + if args.is_empty() { + add_new_wordle(msg, wordle_map).await; + return send_embed_message(ctx, msg, GAME_STARTED_MSG).await; + } + + let number_of_players: usize = args.single_quoted::()?.parse().unwrap(); + if number_of_players <= 1 { + return send_embed_message(ctx, msg, WRONG_PLAYERS_NUMBER_MSG).await; + } + + /* Group can't start a game if there are solo games. */ + if !wordle_map.lock().await.games.is_empty() { + return send_embed_message(ctx, msg, SOLO_PLAYING_MSG).await; + } + + /* If there is a start for a group play, games map will contain + * UserId of a person who initiated a game. */ + add_new_wordle(msg, wordle_map).await; + wordle_map.lock().await.max_people_playing = number_of_players; + wordle_map.lock().await.joined_people.push(msg.author.id); + send_embed_message(ctx, msg, WAIT_FOR_PLAYERS_MSG).await +} + +fn check_channel(wordle_map: &MutexGuard<'_, ServerMap>, msg: &Message) -> bool { + for &key in wordle_map.games.keys() { + if key.0 == msg.channel_id { + return true; + } + } + false +} + +/* Changes time for now in a games map. */ +fn change_time(wordle_map: &mut MutexGuard<'_, ServerMap>) { + for (_, (_, time)) in wordle_map.games.iter_mut() { + *time = SystemTime::now(); + } +} + +#[command] +async fn join(ctx: &Context, msg: &Message) -> CommandResult { + let mut wordle_data = ctx.data.write().await; + let mut wordle_map = wordle_data + .get_mut::() + .expect("Failed to retrieve wordle map!") + .lock() + .await; + + check_ended_games(&mut wordle_map); + + /* No one can join if no one initiated a group game. */ + if wordle_map.max_people_playing == 1 { + return send_embed_message(ctx, msg, START_GROUP_MSG).await; + } + + if wordle_map.joined_people.len() == wordle_map.max_people_playing { + return send_embed_message(ctx, msg, GROUP_PLAYING_MSG).await; + } + + if !check_channel(&wordle_map, msg) { + return send_embed_message(ctx, msg, WRONG_CHANNEL_MSG).await; + } + + if wordle_map.joined_people.contains(&msg.author.id) { + return send_embed_message(ctx, msg, ALREADY_JOINED_MSG).await; + } + + wordle_map.joined_people.push(msg.author.id); + if wordle_map.joined_people.len() != wordle_map.max_people_playing { + return send_embed_message( + ctx, + msg, + &format!( + "You successfully joined the group! To start the game wait for {} other people", + wordle_map.max_people_playing - wordle_map.joined_people.len() + ), + ) + .await; + } + + /* If there are enough people in a group, reset the timer and start the game. */ + change_time(&mut wordle_map); + send_embed_message(ctx, msg, GAME_STARTED_MSG).await +} + +fn clean_game(wordle_map: &mut MutexGuard<'_, ServerMap>, msg: &Message, author: UserId) { + wordle_map.games.remove(&(msg.channel_id, author)); + clean_joined_and_max_playing(wordle_map); +} + +#[command] +async fn giveup(ctx: &Context, msg: &Message) -> CommandResult { + let mut wordle_data = ctx.data.write().await; + let mut wordle_map = wordle_data + .get_mut::() + .expect("Failed to retrieve wordle map!") + .lock() + .await; + + let mut author = msg.author.id; + if wordle_map.max_people_playing > 1 { + if !check_channel(&wordle_map, msg) { + return send_embed_message(ctx, msg, GUESS_WRONG_CHANNEL_MSG).await; + } + if !wordle_map.joined_people.contains(&msg.author.id) { + return send_embed_message(ctx, msg, NOT_IN_GROUP_MSG).await; + } + author = wordle_map.joined_people[0]; + } + + let wordle = wordle_map.games.remove(&(msg.channel_id, author)); + if wordle.is_none() { + return send_embed_message(ctx, msg, START_PLAYING_MSG).await; + } + clean_joined_and_max_playing(&mut wordle_map); + + let wordle = &wordle.unwrap().0; + send_wordle_solution( + wordle, + &msg.channel_id, + get_players(&mut wordle_map, author), + &ctx.http, + ) + .await; + Ok(()) +} + +#[command] +async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { + let mut wordle_data = ctx.data.write().await; + let mut wordle_map = wordle_data + .get_mut::() + .expect("Failed to retrieve wordle map!") + .lock() + .await; + + check_ended_games(&mut wordle_map); + + let mut author = msg.author.id; + let players = get_players(&mut wordle_map, author); + + /* Check if a person can guess if there is a group game. */ + if wordle_map.max_people_playing > 1 { + if wordle_map.joined_people.len() != wordle_map.max_people_playing { + return send_embed_message( + ctx, + msg, + &format!( + "To start the game wait for {} other people", + wordle_map.max_people_playing - wordle_map.joined_people.len() + ), + ) + .await; + } + if !check_channel(&wordle_map, msg) { + return send_embed_message(ctx, msg, GUESS_WRONG_CHANNEL_MSG).await; + } + if !wordle_map.joined_people.contains(&msg.author.id) { + return send_embed_message(ctx, msg, NOT_IN_GROUP_MSG).await; + } + author = wordle_map.joined_people[0]; + } + + let words_vector: Vec = wordle_map + .words + .words + .iter() + .map(|word| word.word.clone()) + .collect(); + + /* Word comparison is case insensitive. */ + let guess = args.single_quoted::()?.to_uppercase(); + let mut string_response = Builder::default(); + + if guess.len() != DEFAULT_SIZE || !guess.chars().all(char::is_alphabetic) { + return send_embed_message(ctx, msg, INCORRECT_GUESS_MSG).await; + } + if !words_vector.contains(&guess) { + return send_embed_message(ctx, msg, NOT_IN_LIST_MSG).await; + } + + let wordle = wordle_map.games.get_mut(&(msg.channel_id, author)); + if wordle.is_none() { + return send_embed_message(ctx, msg, START_PLAYING_MSG).await; + } + + let mut wordle = &mut wordle.unwrap().0; + wordle.guesses += 1; + + /* Processing and saving the guess, then sending a reply to the same channel the guess was sent to. */ + if guess.eq(&wordle.word) { + /* The guess was entirely correct */ + string_response.append(WON_MSG); + if let Err(why) = send_message(WON_MSG, Some(players), &ctx.http, &msg.channel_id).await { + println!("Error sending the message: {}", why); + } + clean_game(&mut wordle_map, msg, author); + } else if wordle.guesses == GUESSES { + /* The player ran out of guesses. */ + if let Err(why) = send_message(TOO_MANY_GUESSES_MSG, None, &ctx.http, &msg.channel_id) + .and_then(|_| async move { + send_wordle_solution(wordle, &msg.channel_id, players, &ctx.http).await; + Ok(()) + }) + .await + { + println!("Error sending the message: {}", why); + } + clean_game(&mut wordle_map, msg, author); + } else { + /* Other cases. */ + wordle.add_fields(guess); + if let Err(why) = send_string( + &ctx.http, + &msg.channel_id, + display_wordle(wordle, players).as_str(), + ) + .and_then(|message| async move { + react_to_message(&ctx.http, &message, wordle).await; + Ok(()) + }) + .await + { + println!("Error sending the message: {}", why); + } + } + Ok(()) +} + +/* Declaration of a set of available commands. */ +#[group("public")] +#[commands(start, guess, help, join, giveup)] +struct Public; + +#[tokio::main] +async fn main() { + let config = Config::load().unwrap(); + let mut client = ClientBuilder::new( + config.token(), + GatewayIntents::GUILD_MESSAGES + .union(GatewayIntents::MESSAGE_CONTENT) + .union(GatewayIntents::GUILD_MESSAGE_REACTIONS), + ) + .event_handler(Handler) + .framework( + StandardFramework::new() + .configure(|c| c.with_whitespace(true).prefix(config.prefix())) + .group(&PUBLIC_GROUP), + ) + .type_map_insert::(Arc::new(Mutex::new(ServerMap::new().await))) + .await + .expect("Couldn't create the new client!"); + + if let Err(why) = client.start().await { + println!("Client error: {}", why) + } +} diff --git a/src/messages.rs b/src/messages.rs new file mode 100644 index 0000000..a2116cf --- /dev/null +++ b/src/messages.rs @@ -0,0 +1,182 @@ +use crate::wordle::{DEFAULT_SIZE, GUESSES}; +use crate::Wordle; +use const_format::formatcp; +use serde_json::{json, Value}; +use serenity::framework::standard::CommandResult; +use serenity::http::Http; +use serenity::model::prelude::{ChannelId, Message, ReactionType, UserId}; +use serenity::prelude::{Context, SerenityError}; +use serenity::utils::Colour; + +use string_builder::Builder; + +/* Messages send by bot. */ +pub const HELLO_MSG: &str = "Hello, I'm a Wordle Bot"; +pub const HELP_MSG: &str = formatcp!("Type `!start` to start the game.\n**Rules:**\nYou have {} tries to guess a {}-letter word in 5 minutes.\n\ + To guess type `!guess [Your guess]`.\nAfter each guess the color of the letters will change to show how close your guess was to the word.\n\ + If the letter is **green**, it is in the word and in the correct spot.\nIf the letter is **yellow**, it is in the word but in the wrong spot.\n\ + If the letter is **red**, it is not in the word in any spot.\n\n\ + If you want to give up, type `!giveup` or click on the white flag emoji under the latest display of your Wordle.\n + Type `!start ` to start a game with friends.\n\ + **Additional rules for groups:**\nYou have 5 minutes to gather a specified number of players.\nTo join a group type `!join`.\n\ + A group can only play if there are no solo games and if there are no other groups playing.", GUESSES, DEFAULT_SIZE); +pub const GROUP_PLAYING_MSG: &str = "A group is playing, wait for the game to finish!"; +pub const GAME_STARTED_MSG: &str = "Game started! Take a guess using `!guess [Your guess]`."; +pub const WRONG_PLAYERS_NUMBER_MSG: &str = "If you want to play alone type `!start`! \ + If you want to play in a group, you need at least two players!"; +pub const SOLO_PLAYING_MSG: &str = "Someone is playing, wait for the game(s) to finish!"; +pub const WAIT_FOR_PLAYERS_MSG: &str = "Wait for other players to start the game! \ + To join a game type `!join`."; +pub const ALREADY_JOINED_MSG: &str = "You already joined a group!"; +pub const WRONG_CHANNEL_MSG: &str = "If you want to join your friends type `!join` \ + on a channel where the game was initiated!"; +pub const START_GROUP_MSG: &str = "To start playing with friends type `!start `"; +pub const GUESS_WRONG_CHANNEL_MSG: &str = "Type your guess on a channel where the game started!"; +pub const NOT_IN_GROUP_MSG: &str = "You can't giveup! You are not in a game!"; +pub const INCORRECT_GUESS_MSG: &str = "Guess word must contain 5 letters without numbers"; +pub const NOT_IN_LIST_MSG: &str = "Guess word is not in word list"; +pub const START_PLAYING_MSG: &str = "If you want to play alone type `!start`! \ + To start playing with friends, type `!start `!"; +pub const WON_MSG: &str = "you won! 🎉"; +pub const TOO_MANY_GUESSES_MSG: &str = "You ran out of guesses!"; +pub const YOUR_GUESSES_MSG: &str = " your guesses: \n"; +pub const GUESS_AGAIN: &str = "Guess again!"; +const DICTIONARY_REQUEST: &str = "https://api.dictionaryapi.dev/api/v2/entries/en/"; + +/* Sends the contents of message_builder to a channel. */ +pub async fn send_builder_contents( + http: &Http, + channel: &ChannelId, + message_builder: Builder, +) -> Result { + channel.say(http, message_builder.string().unwrap()).await +} + +/* Sends a message the given string holds. */ +pub async fn send_string( + http: &Http, + channel: &ChannelId, + message: &str, +) -> Result { + channel.say(http, message).await +} + +pub async fn send_message( + message: &str, + players: Option>, + http: &Http, + channel: &ChannelId, +) -> Result { + let mut builder = Builder::default(); + if let Some(v) = players { + list_players(&mut builder, v); + } + builder.append(message); + send_builder_contents(http, channel, builder).await +} + +/* Adds a white flag reaction under a message. + * The message is supposed to display the current state of the game. */ +pub async fn react_to_message(http: &Http, message: &Message, wordle: &mut Wordle) { + wordle.last_message_id = Some(message.id); + if let Err(why) = message + .react(http, ReactionType::Unicode(String::from("🏳"))) + .await + { + println!("Could not react to the message; {}", why); + } +} + +/* Appends comma-separated mentions of players to the builder. */ +fn list_players(builder: &mut Builder, players: Vec) { + for player in players { + builder.append(format!("<@{}>, ", &player.0)); + } +} + +/* Displays current state of a wordle. */ +pub fn display_wordle(wordle: &Wordle, players: Vec) -> String { + let mut builder = Builder::default(); + list_players(&mut builder, players); + builder.append(YOUR_GUESSES_MSG); + wordle.display_game(&mut builder); + builder.append(GUESS_AGAIN); + builder.string().unwrap() +} + +/* Fetches a definition for a given word from a dictionary API. + * Returns the first definition found. + * If there has been an error, returns an empty String. */ +async fn get_definition(word: &str) -> String { + let mut definition = String::from(""); + let default = json!(""); + let mut url = String::from(DICTIONARY_REQUEST); + url.push_str(word); + let request = reqwest::get(url).await; + match request { + Err(why) => { + println!("Error fetching the definition: {}", why) + } + Ok(response) => { + if let Err(why) = response + .json::() + .await + .map(|json_value| { + json_value + .pointer("/0/meanings/0/definitions/0/definition") + .unwrap_or(&default) + .clone() + }) + .map(|value| { + definition = value.to_string(); + }) + { + println!("Error reading the definition: {}", why); + } + } + } + definition +} + +/* Sends the solution to given Wordle to the given channel. + * The channel is supposed to be the same one in which the game is happening. */ +pub async fn send_wordle_solution( + wordle: &Wordle, + channel: &ChannelId, + players: Vec, + http: &Http, +) { + let mut builder = Builder::default(); + list_players(&mut builder, players); + builder.append("your word was:"); + if let Ok(string) = builder.string() { + let definition = get_definition(wordle.word.as_str()).await; + + if let Err(why) = channel + .send_message(http, |m| { + m.content(string).embed(|e| { + e.title(wordle.word.as_str()) + .description(definition) + .color(Colour::new(0xff6905)) + }) + }) + .await + { + println!("Error sending the message: {}", why); + } + } else { + } +} + +pub async fn send_embed_message(ctx: &Context, msg: &Message, message: &str) -> CommandResult { + if let Err(why) = msg + .channel_id + .send_message(ctx, |m| { + m.embed(|e| e.title(HELLO_MSG).description(message)) + }) + .await + { + println!("Error sending the help message: {}", why); + } + Ok(()) +} diff --git a/src/wordle.rs b/src/wordle.rs new file mode 100644 index 0000000..9654fa6 --- /dev/null +++ b/src/wordle.rs @@ -0,0 +1,93 @@ +use serenity::model::id::MessageId; +use std::collections::HashMap; +use string_builder::Builder; + +pub enum Result { + Green, + Yellow, + Red, +} + +pub const DEFAULT_SIZE: usize = 5; +pub const GUESSES: u32 = 6; +pub const GREEN_SQUARE: &str = ":green_square: "; +pub const YELLOW_SQUARE: &str = ":yellow_square: "; +pub const RED_SQUARE: &str = ":red_square: "; + +/* Struct representing a single char in guess word. */ +pub struct Field { + pub letter: char, + pub square: Result, +} + +impl Field { + pub fn new(letter: char, square: Result) -> Field { + Field { letter, square } + } +} + +/* Struct representing a single instance of the game. */ +pub struct Wordle { + pub word: String, + pub guesses: u32, + pub fields: HashMap>, + pub last_message_id: Option, +} + +impl Wordle { + pub fn new(word: String) -> Wordle { + Wordle { + word, + guesses: 0, + fields: HashMap::new(), + last_message_id: None, + } + } + + /* Saves guess word as Fields with corresponding color describing if char + * matches the chars in a word to guess. */ + pub fn add_fields(&mut self, guess: String) { + let mut field_vec = Vec::new(); + for (pos, c) in guess.chars().enumerate() { + let field: Field; + if c == self.word.chars().nth(pos).unwrap() { + field = Field::new(c, Result::Green); + } else if self.word.chars().any(|word_c| word_c == c) { + field = Field::new(c, Result::Yellow); + } else { + field = Field::new(c, Result::Red); + } + field_vec.push(field); + } + self.fields.insert(self.guesses, field_vec); + } + + pub fn display_game(&self, string_response: &mut Builder) { + for round in 1..(GUESSES + 1) { + if self.guesses >= round { + let vec_fields = self.fields.get(&round).unwrap(); + /* Displays guessed word. */ + for field in vec_fields { + string_response.append(format!("{} ", field.letter)); + } + string_response.append('\n'); + + /* Displays different colored squares depending on square value in Field. */ + for field in vec_fields { + match field.square { + Result::Red => { + string_response.append(RED_SQUARE); + } + Result::Yellow => { + string_response.append(YELLOW_SQUARE); + } + Result::Green => { + string_response.append(GREEN_SQUARE); + } + } + } + string_response.append('\n'); + } + } + } +} diff --git a/src/words.rs b/src/words.rs new file mode 100644 index 0000000..30caaa9 --- /dev/null +++ b/src/words.rs @@ -0,0 +1,43 @@ +use bracket_random::prelude::RandomNumberGenerator; +use serde::Deserialize; + +/* Struct representing a single available word. */ +#[derive(Deserialize)] +pub struct Word { + pub word: String, +} + +/* Struct representing all available words to guess. */ +pub struct Words { + pub words: Vec, +} + +impl Words { + pub async fn new() -> Words { + /* Get list of available words in json format. */ + let result = reqwest::get( + "https://raw.githubusercontent.com/mongodb-developer/bash-wordle/main/words.json", + ) + .await; + + match result { + Err(why) => { + println!("Error fetching data: {}", why); + Words { + words: vec![Word { + word: String::from("EMPTY"), + }], + } + } + Ok(response) => Words { + words: response.json().await.expect("Error parsing list of words"), + }, + } + } + + pub fn generate_word(&self) -> &Word { + let mut rng = RandomNumberGenerator::new(); + rng.random_slice_entry(&self.words) + .expect("Error getting random word") + } +}