From 2be4b2dc74b7b3547d1edbea7e3ec090b2d153d6 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Tue, 12 Apr 2022 18:15:26 +0000 Subject: [PATCH 01/51] Setting up GitHub Classroom Feedback From 9468c6fc95806790876b5ac80ad76bcc68069c7b Mon Sep 17 00:00:00 2001 From: mielnikk <93001127+mielnikk@users.noreply.github.com> Date: Tue, 12 Apr 2022 20:34:22 +0200 Subject: [PATCH 02/51] initial plan --- README.md | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 7c2efa5..16d7824 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,21 @@ -# Frobnicator +# Discord Game Bot ## Autorzy -- Andrzej Głuszak (gr 9, @agluszak na githubie) -- Linus Torvalds (Uniwersytet Helsiński, @torvalds na githubie) +- Katarzyna Mielnik (gr 4, @mielnikk na githubie) +- Julia Podrażka (gr 4, @julia-podrazka na githubie) ## Opis -Od zawsze chcieliśmy napisać grę komputerową. -Frobnicator będzie to gra platformowa, w której chodzi o to, żeby... +Plan jest taki, że napiszemy bota do Discorda umożliwiającego interaktywne granie w pewne gry. Generalnie muszą to być gry, które łatwo można przedstawić w tekstowym formacie. -Z grubsza będziemy wzorować się na [tym tutorialu](https://dev.to/sbelzile/rust-platformer-part-1-bevy-and-ecs-2pci). ## Funkcjonalność -- Generowanie map -- Strzelanie -- AI dla wrogów (bardziej rozbudowane niż w tutorialu) -- Możliwość zapisywania i wczytywania stanu gry -- Punktacja +- przeprowadzanie rozgrywki +- może jakieś rankingi dla ludzi na serwerze?? ## Propozycja podziału na części -W pierwszej części stworzymy grę opartą na tutorialu (z lepszym AI) i jedną zahardcodowaną planszą. +W pierwszej części Wordle. -W drugiej części dodamy do tego losowy generator map, zapisywanie/wczytywanie stanu gry oraz system punktacji. +W drugiej części dodamy więcej gier i ewentualnie coś jeszcze. ## Biblioteki -- Bevy -- może coś do serializacji danych? (czy mógłby Pan coś polecić?) +Jesteśmy otwarte na sugestie. From d697f5a8932fee3c49088888a4c7e597558e5106 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sat, 28 May 2022 18:56:32 +0200 Subject: [PATCH 03/51] empty project with serenity dependency --- .gitignore | 1 + Cargo.lock | 1365 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 20 + src/main.rs | 0 4 files changed, 1386 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..cef54a1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1365 @@ +# 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.2+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 = "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_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 = [ + "serenity", +] + +[[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 = "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", + "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.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[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..2e4c6dc --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[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] +[dependencies.serenity] +git = "https://github.com/serenity-rs/serenity" +branch = "current" +features = [ + "builder", + "cache", + "framework", + "model", + "standard_framework", + "rustls_backend", + "utils", +] \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e69de29 From 6b74035659105d025c21b4325d624ebefc41c169 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sat, 28 May 2022 21:41:53 +0200 Subject: [PATCH 04/51] added config loading --- Cargo.lock | 29 +++++++++++++++++++--- Cargo.toml | 1 + src/config.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 24 ++++++++++++++++++ 4 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 src/config.rs diff --git a/Cargo.lock b/Cargo.lock index cef54a1..7be743e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +dependencies = [ + "byteorder", +] + [[package]] name = "base64" version = "0.13.0" @@ -622,6 +631,7 @@ dependencies = [ name = "project-dc-bot" version = "0.1.0" dependencies = [ + "ron", "serenity", ] @@ -679,7 +689,7 @@ version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" dependencies = [ - "base64", + "base64 0.13.0", "bytes", "encoding_rs", "futures-core", @@ -728,6 +738,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "ron" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ece421e0c4129b90e4a35b6f625e472e96c552136f5093a2f4fa2bbb75a62d5" +dependencies = [ + "base64 0.10.1", + "bitflags", + "serde", +] + [[package]] name = "rustls" version = "0.20.6" @@ -746,7 +767,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" dependencies = [ - "base64", + "base64 0.13.0", ] [[package]] @@ -831,7 +852,7 @@ source = "git+https://github.com/serenity-rs/serenity?branch=current#5eb6118ff7d dependencies = [ "async-trait", "async-tungstenite", - "base64", + "base64 0.13.0", "bitflags", "bytes", "cfg-if", @@ -1079,7 +1100,7 @@ version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" dependencies = [ - "base64", + "base64 0.13.0", "byteorder", "bytes", "http", diff --git a/Cargo.toml b/Cargo.toml index 2e4c6dc..c121232 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ron = "0.5.1" [dependencies.serenity] git = "https://github.com/serenity-rs/serenity" branch = "current" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..fceadd9 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,69 @@ +mod private { + pub const PREFIX: &'static str = "!"; + pub const TOKEN: &'static str = "OTc2MjI1OTg0ODYyODc5ODI0.G6YFle.YGmB0wvOvC_BgfqSNkRXOw4w75aUHPq1QKme0M"; +} + +use ron::{ser, de}; +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Config { + token: &'static str, + prefix: &'static str, +} + +impl Config { + pub fn new() -> Self { + return Config { + token: private::TOKEN, + prefix: private::PREFIX, + }; + } + + pub fn token(&self) -> &'static str { + return self.token; + } + + pub fn prefix(&self) -> &'static str { + return self.prefix; + } + + /* Saves the configuration data into 'config.ron'. */ + pub fn save(&self) -> std::io::Result<()> { + let data = Config { + token: private::TOKEN, + prefix: private::PREFIX, + }; + + let pretty = ser::PrettyConfig::new() + .with_depth_limit(2) + .with_separate_tuple_members(true) + .with_enum_arrays(true); + + let s = ser::to_string_pretty(&data, pretty) + .expect("Serialization failed!"); + + let mut file = std::fs::File::create("config.ron")?; + if let Err(why) = write!(file, "{}", s) { + println!("Failed writing to file: {}", why); + } else { + println!("Write operation succeeded!"); + } + return Ok(()); + } + + /* 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); + } + }; + + return Ok(config); + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e69de29..1b48c7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -0,0 +1,24 @@ +mod config; +use config::Config; +use serenity::{ + prelude::*, + model::prelude::*, + Client, +}; + +struct Handler; +impl EventHandler for Handler { + fn message(&self, context: Context, msg: Message) { + unimplemented!(); + } +} + +fn main() { + let _ = Config::new().save(); + let config = Config::load().unwrap(); + let mut client = Client::new(config.token(), Handler) + .expect("Couldn't create the new client!"); + if let Err(why) = client.start() { + println!("Client error: {}", why) + } +} \ No newline at end of file From 03ddbeedc965048af37d445adde8ebfb165c7d7c Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sun, 29 May 2022 01:18:54 +0200 Subject: [PATCH 05/51] fixed client initialization --- Cargo.lock | 123 ++++++++++++++++++++++++++++++-------------------- Cargo.toml | 6 ++- src/config.rs | 29 ++++++------ src/main.rs | 32 +++++++++---- 4 files changed, 119 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7be743e..b48e71d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,8 +28,8 @@ dependencies = [ "futures-io", "futures-util", "log", - "pin-project-lite", - "tokio", + "pin-project-lite 0.2.9", + "tokio 1.18.2", "tokio-rustls", "tungstenite", "webpki-roots", @@ -41,15 +41,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "base64" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder", -] - [[package]] name = "base64" version = "0.13.0" @@ -83,6 +74,12 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + [[package]] name = "bytes" version = "1.1.0" @@ -272,7 +269,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite", + "pin-project-lite 0.2.9", "pin-utils", "slab", ] @@ -295,7 +292,7 @@ checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if", "libc", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi 0.10.0+wasi-snapshot-preview1", ] [[package]] @@ -304,7 +301,7 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" dependencies = [ - "bytes", + "bytes 1.1.0", "fnv", "futures-core", "futures-sink", @@ -312,7 +309,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio", + "tokio 1.18.2", "tokio-util 0.7.2", "tracing", ] @@ -335,7 +332,7 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff8670570af52249509a86f5e3e18a08c60b177071826898fde8997cf5f6bfbb" dependencies = [ - "bytes", + "bytes 1.1.0", "fnv", "itoa", ] @@ -346,9 +343,9 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes", + "bytes 1.1.0", "http", - "pin-project-lite", + "pin-project-lite 0.2.9", ] [[package]] @@ -369,7 +366,7 @@ version = "0.14.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" dependencies = [ - "bytes", + "bytes 1.1.0", "futures-channel", "futures-core", "futures-util", @@ -379,9 +376,9 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite", + "pin-project-lite 0.2.9", "socket2", - "tokio", + "tokio 1.18.2", "tower-service", "tracing", "want", @@ -396,7 +393,7 @@ dependencies = [ "http", "hyper", "rustls", - "tokio", + "tokio 1.18.2", "tokio-rustls", ] @@ -600,6 +597,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pin-project-lite" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -632,7 +635,9 @@ name = "project-dc-bot" version = "0.1.0" dependencies = [ "ron", + "serde", "serenity", + "tokio 0.2.25", ] [[package]] @@ -689,8 +694,8 @@ version = "0.11.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" dependencies = [ - "base64 0.13.0", - "bytes", + "base64", + "bytes 1.1.0", "encoding_rs", "futures-core", "futures-util", @@ -706,13 +711,13 @@ dependencies = [ "mime", "mime_guess", "percent-encoding", - "pin-project-lite", + "pin-project-lite 0.2.9", "rustls", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", - "tokio", + "tokio 1.18.2", "tokio-rustls", "tokio-util 0.6.10", "url", @@ -740,11 +745,11 @@ dependencies = [ [[package]] name = "ron" -version = "0.5.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ece421e0c4129b90e4a35b6f625e472e96c552136f5093a2f4fa2bbb75a62d5" +checksum = "1b861ecaade43ac97886a512b360d01d66be9f41f3c61088b42cedf92e03d678" dependencies = [ - "base64 0.10.1", + "base64", "bitflags", "serde", ] @@ -767,7 +772,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" dependencies = [ - "base64 0.13.0", + "base64", ] [[package]] @@ -852,9 +857,9 @@ source = "git+https://github.com/serenity-rs/serenity?branch=current#5eb6118ff7d dependencies = [ "async-trait", "async-tungstenite", - "base64 0.13.0", + "base64", "bitflags", - "bytes", + "bytes 1.1.0", "cfg-if", "chrono", "command_attr", @@ -872,7 +877,7 @@ dependencies = [ "serde_json", "static_assertions", "time", - "tokio", + "tokio 1.18.2", "tracing", "typemap_rev", "url", @@ -982,23 +987,45 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "tokio" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" +dependencies = [ + "bytes 0.5.6", + "pin-project-lite 0.1.12", + "tokio-macros 0.2.6", +] + [[package]] name = "tokio" version = "1.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" dependencies = [ - "bytes", + "bytes 1.1.0", "libc", "memchr", "mio", "once_cell", - "pin-project-lite", + "pin-project-lite 0.2.9", "socket2", - "tokio-macros", + "tokio-macros 1.7.0", "winapi", ] +[[package]] +name = "tokio-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio-macros" version = "1.7.0" @@ -1017,7 +1044,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls", - "tokio", + "tokio 1.18.2", "webpki", ] @@ -1027,12 +1054,12 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ - "bytes", + "bytes 1.1.0", "futures-core", "futures-sink", "log", - "pin-project-lite", - "tokio", + "pin-project-lite 0.2.9", + "tokio 1.18.2", ] [[package]] @@ -1041,11 +1068,11 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" dependencies = [ - "bytes", + "bytes 1.1.0", "futures-core", "futures-sink", - "pin-project-lite", - "tokio", + "pin-project-lite 0.2.9", + "tokio 1.18.2", "tracing", ] @@ -1063,7 +1090,7 @@ checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ "cfg-if", "log", - "pin-project-lite", + "pin-project-lite 0.2.9", "tracing-attributes", "tracing-core", ] @@ -1100,9 +1127,9 @@ version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" dependencies = [ - "base64 0.13.0", + "base64", "byteorder", - "bytes", + "bytes 1.1.0", "http", "httparse", "log", @@ -1206,9 +1233,9 @@ dependencies = [ [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasi" diff --git a/Cargo.toml b/Cargo.toml index c121232..5da029d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -ron = "0.5.1" +ron = "0.7.0" +serde = "1.0.137" +tokio = { version = "0.2", features = ["macros"] } [dependencies.serenity] git = "https://github.com/serenity-rs/serenity" branch = "current" @@ -18,4 +20,6 @@ features = [ "standard_framework", "rustls_backend", "utils", + "client", + "gateway" ] \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index fceadd9..b164696 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,42 +3,43 @@ mod private { pub const TOKEN: &'static str = "OTc2MjI1OTg0ODYyODc5ODI0.G6YFle.YGmB0wvOvC_BgfqSNkRXOw4w75aUHPq1QKme0M"; } +use std::io::Write; use ron::{ser, de}; use serde::{Serialize, Deserialize}; #[derive(Debug, Serialize, Deserialize)] pub struct Config { - token: &'static str, - prefix: &'static str, + token: String, + prefix: String, } impl Config { pub fn new() -> Self { return Config { - token: private::TOKEN, - prefix: private::PREFIX, + token: String::from(private::TOKEN), + prefix: String::from(private::PREFIX), }; } - pub fn token(&self) -> &'static str { - return self.token; + pub fn token(&self) -> &str { + return self.token.as_str(); } - pub fn prefix(&self) -> &'static str { - return self.prefix; + pub fn prefix(&self) -> &str { + return self.prefix.as_str(); } /* Saves the configuration data into 'config.ron'. */ pub fn save(&self) -> std::io::Result<()> { let data = Config { - token: private::TOKEN, - prefix: private::PREFIX, + token: String::from(private::TOKEN), + prefix: String::from(private::PREFIX), }; let pretty = ser::PrettyConfig::new() - .with_depth_limit(2) - .with_separate_tuple_members(true) - .with_enum_arrays(true); + .depth_limit(2) + .separate_tuple_members(true) + .enumerate_arrays(true); let s = ser::to_string_pretty(&data, pretty) .expect("Serialization failed!"); @@ -56,7 +57,7 @@ impl Config { 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) { + let config: Config = match de::from_reader(&f) { Ok(x) => x, Err(e) => { println!("Failed to load config: {}", e); diff --git a/src/main.rs b/src/main.rs index 1b48c7d..611c362 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,22 +3,38 @@ use config::Config; use serenity::{ prelude::*, model::prelude::*, - Client, + client::ClientBuilder, + framework::standard::{ + CommandResult, macros::command, macros::group, StandardFramework, + } }; -struct Handler; -impl EventHandler for Handler { - fn message(&self, context: Context, msg: Message) { - unimplemented!(); +#[command] +async fn start(ctx: & Context, msg: &Message) -> CommandResult { + if let Err(why) = msg.channel_id.say(&ctx.http, "jazda").await { + println!("Error sending message: {}", why); } + return Ok(()); } -fn main() { +/* Declaration of a set of available commands. */ +#[group] +#[commands(start)] +struct Public; + +#[tokio::main] +async fn main() { let _ = Config::new().save(); let config = Config::load().unwrap(); - let mut client = Client::new(config.token(), Handler) + let mut client = ClientBuilder::new(config.token(), GatewayIntents::default()) + .framework(StandardFramework::new() + .configure(|c| + c.prefix(config.prefix())) + .group(&PUBLIC_GROUP)) + .await .expect("Couldn't create the new client!"); - if let Err(why) = client.start() { + if let Err(why) = client.start().await { println!("Client error: {}", why) } + } \ No newline at end of file From 502f7a3f67dbd6bc5239c5fb6b85191d9207461e Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sun, 29 May 2022 01:47:00 +0200 Subject: [PATCH 06/51] fixed library versions --- Cargo.lock | 114 +++++++++++++++++++++++------------------------------ Cargo.toml | 4 +- 2 files changed, 52 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b48e71d..d3f7fa6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,8 +28,8 @@ dependencies = [ "futures-io", "futures-util", "log", - "pin-project-lite 0.2.9", - "tokio 1.18.2", + "pin-project-lite", + "tokio", "tokio-rustls", "tungstenite", "webpki-roots", @@ -74,12 +74,6 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" -[[package]] -name = "bytes" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" - [[package]] name = "bytes" version = "1.1.0" @@ -269,7 +263,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.9", + "pin-project-lite", "pin-utils", "slab", ] @@ -301,7 +295,7 @@ version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" dependencies = [ - "bytes 1.1.0", + "bytes", "fnv", "futures-core", "futures-sink", @@ -309,7 +303,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 1.18.2", + "tokio", "tokio-util 0.7.2", "tracing", ] @@ -326,13 +320,22 @@ 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 1.1.0", + "bytes", "fnv", "itoa", ] @@ -343,9 +346,9 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.1.0", + "bytes", "http", - "pin-project-lite 0.2.9", + "pin-project-lite", ] [[package]] @@ -366,7 +369,7 @@ version = "0.14.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42dc3c131584288d375f2d07f822b0cb012d8c6fb899a5b9fdb3cb7eb9b6004f" dependencies = [ - "bytes 1.1.0", + "bytes", "futures-channel", "futures-core", "futures-util", @@ -376,9 +379,9 @@ dependencies = [ "httparse", "httpdate", "itoa", - "pin-project-lite 0.2.9", + "pin-project-lite", "socket2", - "tokio 1.18.2", + "tokio", "tower-service", "tracing", "want", @@ -393,7 +396,7 @@ dependencies = [ "http", "hyper", "rustls", - "tokio 1.18.2", + "tokio", "tokio-rustls", ] @@ -544,6 +547,16 @@ 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" @@ -597,12 +610,6 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" -[[package]] -name = "pin-project-lite" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" - [[package]] name = "pin-project-lite" version = "0.2.9" @@ -637,7 +644,7 @@ dependencies = [ "ron", "serde", "serenity", - "tokio 0.2.25", + "tokio", ] [[package]] @@ -695,7 +702,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb" dependencies = [ "base64", - "bytes 1.1.0", + "bytes", "encoding_rs", "futures-core", "futures-util", @@ -711,13 +718,13 @@ dependencies = [ "mime", "mime_guess", "percent-encoding", - "pin-project-lite 0.2.9", + "pin-project-lite", "rustls", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", - "tokio 1.18.2", + "tokio", "tokio-rustls", "tokio-util 0.6.10", "url", @@ -859,7 +866,7 @@ dependencies = [ "async-tungstenite", "base64", "bitflags", - "bytes 1.1.0", + "bytes", "cfg-if", "chrono", "command_attr", @@ -877,7 +884,7 @@ dependencies = [ "serde_json", "static_assertions", "time", - "tokio 1.18.2", + "tokio", "tracing", "typemap_rev", "url", @@ -987,45 +994,24 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" -[[package]] -name = "tokio" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" -dependencies = [ - "bytes 0.5.6", - "pin-project-lite 0.1.12", - "tokio-macros 0.2.6", -] - [[package]] name = "tokio" version = "1.18.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" dependencies = [ - "bytes 1.1.0", + "bytes", "libc", "memchr", "mio", + "num_cpus", "once_cell", - "pin-project-lite 0.2.9", + "pin-project-lite", "socket2", - "tokio-macros 1.7.0", + "tokio-macros", "winapi", ] -[[package]] -name = "tokio-macros" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "tokio-macros" version = "1.7.0" @@ -1044,7 +1030,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls", - "tokio 1.18.2", + "tokio", "webpki", ] @@ -1054,12 +1040,12 @@ version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" dependencies = [ - "bytes 1.1.0", + "bytes", "futures-core", "futures-sink", "log", - "pin-project-lite 0.2.9", - "tokio 1.18.2", + "pin-project-lite", + "tokio", ] [[package]] @@ -1068,11 +1054,11 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" dependencies = [ - "bytes 1.1.0", + "bytes", "futures-core", "futures-sink", - "pin-project-lite 0.2.9", - "tokio 1.18.2", + "pin-project-lite", + "tokio", "tracing", ] @@ -1090,7 +1076,7 @@ checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" dependencies = [ "cfg-if", "log", - "pin-project-lite 0.2.9", + "pin-project-lite", "tracing-attributes", "tracing-core", ] @@ -1129,7 +1115,7 @@ checksum = "d96a2dea40e7570482f28eb57afbe42d97551905da6a9400acc5c328d24004f5" dependencies = [ "base64", "byteorder", - "bytes 1.1.0", + "bytes", "http", "httparse", "log", diff --git a/Cargo.toml b/Cargo.toml index 5da029d..098dc83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,8 @@ edition = "2021" [dependencies] ron = "0.7.0" -serde = "1.0.137" -tokio = { version = "0.2", features = ["macros"] } +serde = "1.0.130" +tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.serenity] git = "https://github.com/serenity-rs/serenity" branch = "current" From 1227b1fd18d2fe8000778e7b7bfc92edd3712316 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sun, 29 May 2022 02:22:17 +0200 Subject: [PATCH 07/51] fixed command group name --- src/config.rs | 26 +++++++++++++------------- src/main.rs | 26 ++++++++++++-------------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/config.rs b/src/config.rs index b164696..4d60565 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,11 +1,12 @@ mod private { - pub const PREFIX: &'static str = "!"; - pub const TOKEN: &'static str = "OTc2MjI1OTg0ODYyODc5ODI0.G6YFle.YGmB0wvOvC_BgfqSNkRXOw4w75aUHPq1QKme0M"; + pub const PREFIX: &str = "!"; + pub const TOKEN: &str = + "OTc2MjI1OTg0ODYyODc5ODI0.G6YFle.YGmB0wvOvC_BgfqSNkRXOw4w75aUHPq1QKme0M"; } +use ron::{de, ser}; +use serde::{Deserialize, Serialize}; use std::io::Write; -use ron::{ser, de}; -use serde::{Serialize, Deserialize}; #[derive(Debug, Serialize, Deserialize)] pub struct Config { @@ -15,18 +16,18 @@ pub struct Config { impl Config { pub fn new() -> Self { - return Config { + Config { token: String::from(private::TOKEN), prefix: String::from(private::PREFIX), - }; + } } pub fn token(&self) -> &str { - return self.token.as_str(); + self.token.as_str() } pub fn prefix(&self) -> &str { - return self.prefix.as_str(); + self.prefix.as_str() } /* Saves the configuration data into 'config.ron'. */ @@ -41,8 +42,7 @@ impl Config { .separate_tuple_members(true) .enumerate_arrays(true); - let s = ser::to_string_pretty(&data, pretty) - .expect("Serialization failed!"); + let s = ser::to_string_pretty(&data, pretty).expect("Serialization failed!"); let mut file = std::fs::File::create("config.ron")?; if let Err(why) = write!(file, "{}", s) { @@ -50,7 +50,7 @@ impl Config { } else { println!("Write operation succeeded!"); } - return Ok(()); + Ok(()) } /* Deserializes the configuration data from 'config.ron' and initializes app's settings. */ @@ -65,6 +65,6 @@ impl Config { } }; - return Ok(config); + Ok(config) } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index 611c362..1ed13cc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,22 @@ mod config; use config::Config; use serenity::{ - prelude::*, - model::prelude::*, client::ClientBuilder, - framework::standard::{ - CommandResult, macros::command, macros::group, StandardFramework, - } + framework::standard::{macros::command, macros::group, CommandResult, StandardFramework}, + model::prelude::*, + prelude::*, }; #[command] -async fn start(ctx: & Context, msg: &Message) -> CommandResult { +async fn start(ctx: &Context, msg: &Message) -> CommandResult { if let Err(why) = msg.channel_id.say(&ctx.http, "jazda").await { println!("Error sending message: {}", why); } - return Ok(()); + Ok(()) } /* Declaration of a set of available commands. */ -#[group] +#[group("public")] #[commands(start)] struct Public; @@ -27,14 +25,14 @@ async fn main() { let _ = Config::new().save(); let config = Config::load().unwrap(); let mut client = ClientBuilder::new(config.token(), GatewayIntents::default()) - .framework(StandardFramework::new() - .configure(|c| - c.prefix(config.prefix())) - .group(&PUBLIC_GROUP)) + .framework( + StandardFramework::new() + .configure(|c| c.with_whitespace(true).prefix(config.prefix())) + .group(&PUBLIC_GROUP), + ) .await .expect("Couldn't create the new client!"); if let Err(why) = client.start().await { println!("Client error: {}", why) } - -} \ No newline at end of file +} From 058eb84549edf6358abc59edcc2e6ec1958e4ba1 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sun, 29 May 2022 02:59:07 +0200 Subject: [PATCH 08/51] added necessary gateway intent --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 1ed13cc..9b0f489 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,7 +24,7 @@ struct Public; async fn main() { let _ = Config::new().save(); let config = Config::load().unwrap(); - let mut client = ClientBuilder::new(config.token(), GatewayIntents::default()) + let mut client = ClientBuilder::new(config.token(), GatewayIntents::GUILD_MESSAGES.union(GatewayIntents::MESSAGE_CONTENT)) .framework( StandardFramework::new() .configure(|c| c.with_whitespace(true).prefix(config.prefix())) From bb46c72dc3a84f34eb03bc72295a64b2cdfeaa54 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sun, 29 May 2022 20:29:44 +0200 Subject: [PATCH 09/51] added a wordle struct placeholder --- src/main.rs | 10 ++++++++++ src/wordle.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 src/wordle.rs diff --git a/src/main.rs b/src/main.rs index 9b0f489..1c75797 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,22 @@ mod config; +mod wordle; + +use std::collections::HashMap; use config::Config; +use wordle::Wordle; use serenity::{ client::ClientBuilder, framework::standard::{macros::command, macros::group, CommandResult, StandardFramework}, model::prelude::*, + model::id::*, prelude::*, }; +/* Struct containing information on all instances of Wordle that have been started. */ +struct Server{ + games : HashMap +} + #[command] async fn start(ctx: &Context, msg: &Message) -> CommandResult { if let Err(why) = msg.channel_id.say(&ctx.http, "jazda").await { diff --git a/src/wordle.rs b/src/wordle.rs new file mode 100644 index 0000000..b9499f8 --- /dev/null +++ b/src/wordle.rs @@ -0,0 +1,26 @@ +use std::collections::HashMap; + +enum Result {Green, Yellow, Black} +static DEFAULT_SIZE: u32 = 5; +static GUESSES: u32 = 6; + +struct Field { + letter: char, + square: Result +} + +/* Struct representing a single instance of the game. */ +pub struct Wordle { + word: String, + size: u32, + guesses: u32, + fields: HashMap> +} + +impl Wordle { + + fn new(size: u32) -> Wordle { + /* TODO randomly picking a word of given size */ + !unimplemented!() + } +} \ No newline at end of file From d5cf81d68aef827ce8408fc49d5c0941cb295500 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Mon, 30 May 2022 06:01:18 +0200 Subject: [PATCH 10/51] Generating words --- src/words | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/words diff --git a/src/words b/src/words new file mode 100644 index 0000000..44d9276 --- /dev/null +++ b/src/words @@ -0,0 +1,32 @@ +use serde::Deserialize; +use bracket_random::prelude::RandomNumberGenerator; +use tokio::task::spawn_blocking; + +#[derive(Deserialize)] +pub struct Word { + word: String, +} + +pub struct Words { + words: Vec, +} + +impl Word { + pub fn word(&self) -> String { + self.word.clone() + } +} + +impl Words { + pub async fn new() -> Words { + let response = reqwest::blocking::get("https://raw.githubusercontent.com/mongodb-developer/bash-wordle/main/words.json").unwrap(); + Words { + words: response.json().unwrap(), + } + } + + pub fn generate_word(&self) -> &Word { + let mut rng = RandomNumberGenerator::new(); + rng.random_slice_entry(&self.words).unwrap().clone() + } +} From c285946152cd8443dc996291ed326ed7462678d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Mon, 30 May 2022 06:01:39 +0200 Subject: [PATCH 11/51] Generating words --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index 1c75797..fe1c1f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod config; mod wordle; +mod words; use std::collections::HashMap; use config::Config; From 6f50693f1123d37d055b872e3c2ade944375b259 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Mon, 30 May 2022 06:02:47 +0200 Subject: [PATCH 12/51] Generating words --- Cargo.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 098dc83..15610c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,10 @@ edition = "2021" [dependencies] ron = "0.7.0" -serde = "1.0.130" +serde = { version = "1.0.130", features = ["derive"] } +serde_json = "1.0.81" +bracket-random = "0.8.2" +reqwest = { version = "0.11", features = ["blocking", "json"] } tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.serenity] git = "https://github.com/serenity-rs/serenity" @@ -22,4 +25,4 @@ features = [ "utils", "client", "gateway" -] \ No newline at end of file +] From 6d83922c2bd57f5d78fbda617a0776c895f6f924 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Mon, 30 May 2022 14:31:50 +0200 Subject: [PATCH 13/51] fixed file type --- src/{words => words.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{words => words.rs} (100%) diff --git a/src/words b/src/words.rs similarity index 100% rename from src/words rename to src/words.rs From 28093ccf7ae223d368b73097a1925d608f46e4f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Mon, 30 May 2022 18:12:20 +0200 Subject: [PATCH 14/51] Creating wordle game for new clients --- src/main.rs | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index fe1c1f9..ff03448 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,17 +12,39 @@ use serenity::{ model::id::*, prelude::*, }; +use std::sync::Arc; +use tokio::sync::Mutex; +use serenity::model::id::ChannelId; -/* Struct containing information on all instances of Wordle that have been started. */ -struct Server{ - games : HashMap +struct ServerKey; + +impl TypeMapKey for ServerKey { + type Value = Arc>; } +/* Contains information on all instances of Wordle that have been started. */ +type ServerMap = HashMap<(ChannelId, UserId), Wordle>; + #[command] async fn start(ctx: &Context, msg: &Message) -> CommandResult { - if let Err(why) = msg.channel_id.say(&ctx.http, "jazda").await { + let mut wordle_data = ctx.data.write().await; + let wordle_map = wordle_data + .get_mut::() + .expect("Failed to retrieve wordle map!"); + + let wordle = Wordle::new(); + let mut string_response = String::from("Word: "); + string_response.push_str(wordle.word.as_str()); + + if let Err(why) = msg.channel_id.say(&ctx.http, &string_response).await { println!("Error sending message: {}", why); } + + /* Addding new wordle game for new client. */ + wordle_map + .lock() + .await + .insert((msg.channel_id, msg.author.id), wordle); Ok(()) } @@ -41,6 +63,7 @@ async fn main() { .configure(|c| c.with_whitespace(true).prefix(config.prefix())) .group(&PUBLIC_GROUP), ) + .type_map_insert::(Arc::new(Mutex::new(ServerMap::new()))) .await .expect("Couldn't create the new client!"); if let Err(why) = client.start().await { From 68919dfb3a4d7ca9f0cbc437f2a4b0d24f132369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Mon, 30 May 2022 18:12:47 +0200 Subject: [PATCH 15/51] Creating wordle game for new clients --- src/wordle.rs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/wordle.rs b/src/wordle.rs index b9499f8..36d00fe 100644 --- a/src/wordle.rs +++ b/src/wordle.rs @@ -1,26 +1,27 @@ use std::collections::HashMap; -enum Result {Green, Yellow, Black} +pub enum Result {Green, Yellow, Black} static DEFAULT_SIZE: u32 = 5; static GUESSES: u32 = 6; -struct Field { - letter: char, - square: Result +pub struct Field { + pub letter: char, + pub square: Result } /* Struct representing a single instance of the game. */ pub struct Wordle { - word: String, - size: u32, - guesses: u32, - fields: HashMap> + pub word: String, + pub guesses: u32, + pub fields: HashMap> } impl Wordle { - - fn new(size: u32) -> Wordle { - /* TODO randomly picking a word of given size */ - !unimplemented!() + pub fn new() -> Wordle { + Wordle { + word: String::from("ABCDE"), + guesses: 0, + fields: HashMap::new(), + } } -} \ No newline at end of file +} From 2d6332014b68c3d76865e20c5c12b2a4d4791611 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Mon, 30 May 2022 19:10:34 +0200 Subject: [PATCH 16/51] fixed fetching words --- src/words.rs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/words.rs b/src/words.rs index 44d9276..54fc4d5 100644 --- a/src/words.rs +++ b/src/words.rs @@ -1,5 +1,8 @@ +use std::process::Output; use serde::Deserialize; use bracket_random::prelude::RandomNumberGenerator; +use serenity::futures::future::MaybeDone::Future; +use serenity::futures::{StreamExt, TryStreamExt}; use tokio::task::spawn_blocking; #[derive(Deserialize)] @@ -19,9 +22,25 @@ impl Word { impl Words { pub async fn new() -> Words { - let response = reqwest::blocking::get("https://raw.githubusercontent.com/mongodb-developer/bash-wordle/main/words.json").unwrap(); - Words { - words: response.json().unwrap(), + 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 + .or_else(|err| { + println!("Parsing error: {}", err); + Result::Ok(vec![]) + }) + .unwrap() + } + } } } From 5e44215bbab65dcbc38897a40d09f56a4a27db6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Mon, 30 May 2022 19:34:12 +0200 Subject: [PATCH 17/51] Generating random words --- src/wordle.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/wordle.rs b/src/wordle.rs index 36d00fe..1b68bad 100644 --- a/src/wordle.rs +++ b/src/wordle.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; pub enum Result {Green, Yellow, Black} -static DEFAULT_SIZE: u32 = 5; -static GUESSES: u32 = 6; +pub static DEFAULT_SIZE: u32 = 5; +pub static GUESSES: u32 = 6; pub struct Field { pub letter: char, @@ -17,9 +17,9 @@ pub struct Wordle { } impl Wordle { - pub fn new() -> Wordle { + pub fn new(word: String) -> Wordle { Wordle { - word: String::from("ABCDE"), + word, guesses: 0, fields: HashMap::new(), } From b6e07ed451551e1f12f72f9b747ef2bc54ba6ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Mon, 30 May 2022 19:35:47 +0200 Subject: [PATCH 18/51] Generating random words --- src/main.rs | 69 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index ff03448..06607c3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,9 @@ use serenity::{ use std::sync::Arc; use tokio::sync::Mutex; use serenity::model::id::ChannelId; +use serenity::framework::standard::Args; +use crate::wordle::GUESSES; +use crate::words::Words; struct ServerKey; @@ -23,34 +26,80 @@ impl TypeMapKey for ServerKey { } /* Contains information on all instances of Wordle that have been started. */ -type ServerMap = HashMap<(ChannelId, UserId), Wordle>; +struct ServerMap { + games: HashMap<(ChannelId, UserId), Wordle>, + words: Words, +} + +impl ServerMap { + pub async fn new() -> ServerMap { + ServerMap { + games: HashMap::new(), + words: Words::new().await, + } + } +} #[command] async fn start(ctx: &Context, msg: &Message) -> CommandResult { let mut wordle_data = ctx.data.write().await; let wordle_map = wordle_data .get_mut::() - .expect("Failed to retrieve wordle map!"); - - let wordle = Wordle::new(); + .expect("Failed to retrieve wordles map!"); + + let random_word = wordle_map.lock().await.words.generate_word().word(); + let wordle = Wordle::new(random_word.clone()); let mut string_response = String::from("Word: "); - string_response.push_str(wordle.word.as_str()); - + string_response.push_str(random_word.as_str()); + if let Err(why) = msg.channel_id.say(&ctx.http, &string_response).await { println!("Error sending message: {}", why); } - - /* Addding new wordle game for new client. */ wordle_map .lock() .await + .games .insert((msg.channel_id, msg.author.id), wordle); Ok(()) } +#[command] +async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { + let guess = args.single_quoted::()?; + + let mut wordle_data = ctx.data.write().await; + let mut wordle_map = wordle_data + .get_mut::() + .expect("Failed to retrieve wordles map!").lock().await; + let mut wordle = wordle_map.games.get_mut(&(msg.channel_id, msg.author.id)); + + let mut string_response = String::from(""); + if wordle.is_none() { + string_response.push_str("To play the game write !start"); + } else { + let mut wordle = wordle.unwrap(); + wordle.guesses += 1; + if guess.eq(&wordle.word) { + string_response.push_str("You won! 🎉"); + wordle_map.games.remove(&(msg.channel_id, msg.author.id)); + } else if wordle.guesses == GUESSES { + string_response.push_str("You ran out of guesses!\nThe word was: "); + string_response.push_str(wordle.word.as_str()); + wordle_map.games.remove(&(msg.channel_id, msg.author.id)); + } else { + string_response.push_str("Guess again!"); + } + } + + if let Err(why) = msg.channel_id.say(&ctx.http, &string_response).await { + println!("Error sending message: {}", why); + } + Ok(()) +} + /* Declaration of a set of available commands. */ #[group("public")] -#[commands(start)] +#[commands(start, guess)] struct Public; #[tokio::main] @@ -63,7 +112,7 @@ async fn main() { .configure(|c| c.with_whitespace(true).prefix(config.prefix())) .group(&PUBLIC_GROUP), ) - .type_map_insert::(Arc::new(Mutex::new(ServerMap::new()))) + .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 { From e63dc9f6ff90d973682c9cca9bce8ccd8959ec72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Mon, 30 May 2022 19:36:05 +0200 Subject: [PATCH 19/51] Generating random words --- src/words.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/words.rs b/src/words.rs index 54fc4d5..9c3e0ac 100644 --- a/src/words.rs +++ b/src/words.rs @@ -34,10 +34,6 @@ impl Words { Words { words: response.json() .await - .or_else(|err| { - println!("Parsing error: {}", err); - Result::Ok(vec![]) - }) .unwrap() } } From 9f9959e13b1dcc556d51750f08214ad9836186be Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Mon, 30 May 2022 20:02:04 +0200 Subject: [PATCH 20/51] added the 'help' command --- src/main.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 06607c3..019b044 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,6 +40,20 @@ impl ServerMap { } } +#[command] +async fn help(ctx: &Context, msg: &Message) -> CommandResult { + if let Err(why) = + msg.channel_id.send_message(ctx, |m| { + m.embed(|e| { + e.title("Hello, I'm a Wordle Bot") + .description("Type `!start` to start the game.") + }) + }).await { + println!("Error sending the help message: {}", why); + } + Ok(()) +} + #[command] async fn start(ctx: &Context, msg: &Message) -> CommandResult { let mut wordle_data = ctx.data.write().await; @@ -99,7 +113,7 @@ async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { /* Declaration of a set of available commands. */ #[group("public")] -#[commands(start, guess)] +#[commands(start, guess, help)] struct Public; #[tokio::main] From 3539f49d1bb5bd0224842ea73b7c25628972956c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Mon, 30 May 2022 20:03:59 +0200 Subject: [PATCH 21/51] Update main.rs --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 019b044..771f9ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -79,7 +79,7 @@ async fn start(ctx: &Context, msg: &Message) -> CommandResult { #[command] async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { - let guess = args.single_quoted::()?; + let guess = args.single_quoted::()?.to_uppercase(); let mut wordle_data = ctx.data.write().await; let mut wordle_map = wordle_data From 2dc94b8f25f9dd739cac3cc5c1b682c3fabc8209 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Tue, 31 May 2022 00:49:51 +0200 Subject: [PATCH 22/51] Debug + format --- src/main.rs | 132 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 82 insertions(+), 50 deletions(-) diff --git a/src/main.rs b/src/main.rs index 771f9ab..6eab356 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,22 +2,22 @@ mod config; mod wordle; mod words; -use std::collections::HashMap; +use crate::wordle::{DEFAULT_SIZE, GUESSES}; +use crate::words::Words; use config::Config; -use wordle::Wordle; use serenity::{ client::ClientBuilder, - framework::standard::{macros::command, macros::group, CommandResult, StandardFramework}, - model::prelude::*, + framework::standard::{macros::command, macros::group, Args, CommandResult, StandardFramework}, model::id::*, + model::prelude::*, prelude::*, }; +use std::collections::HashMap; use std::sync::Arc; use tokio::sync::Mutex; -use serenity::model::id::ChannelId; -use serenity::framework::standard::Args; -use crate::wordle::GUESSES; -use crate::words::Words; +use wordle::Wordle; + +pub static HELLO_MSG: &str = "Hello, I'm a Wordle Bot"; struct ServerKey; @@ -25,7 +25,8 @@ impl TypeMapKey for ServerKey { type Value = Arc>; } -/* Contains information on all instances of Wordle that have been started. */ +/* Contains information on all instances of Wordle that have been started and +all available words to guess. */ struct ServerMap { games: HashMap<(ChannelId, UserId), Wordle>, words: Words, @@ -40,35 +41,43 @@ impl ServerMap { } } -#[command] -async fn help(ctx: &Context, msg: &Message) -> CommandResult { - if let Err(why) = - msg.channel_id.send_message(ctx, |m| { - m.embed(|e| { - e.title("Hello, I'm a Wordle Bot") - .description("Type `!start` to start the game.") +async fn send_embed_message(ctx: &Context, msg: &Message, message: &str) { + if let Err(why) = msg + .channel_id + .send_message(ctx, |m| { + m.embed(|e| e.title(HELLO_MSG).description(message)) }) - }).await { + .await + { println!("Error sending the help message: {}", why); } +} + +#[command] +async fn help(ctx: &Context, msg: &Message) -> CommandResult { + send_embed_message(ctx, msg, &format!("Type `!start` to start the game.\n**Rules:**\nYou have {} tries to guess a {}-letter word.\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.", GUESSES, DEFAULT_SIZE)).await; Ok(()) } #[command] async fn start(ctx: &Context, msg: &Message) -> 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 wordles map!"); + .expect("Failed to retrieve wordle map!"); - let random_word = wordle_map.lock().await.words.generate_word().word(); - let wordle = Wordle::new(random_word.clone()); - let mut string_response = String::from("Word: "); - string_response.push_str(random_word.as_str()); + let wordle = Wordle::new(wordle_map.lock().await.words.generate_word().word.clone()); - if let Err(why) = msg.channel_id.say(&ctx.http, &string_response).await { - println!("Error sending message: {}", why); - } + send_embed_message( + ctx, + msg, + "Game started! Take a guess using `!guess [Your guess]`.", + ) + .await; wordle_map .lock() .await @@ -79,29 +88,49 @@ async fn start(ctx: &Context, msg: &Message) -> CommandResult { #[command] async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { - let guess = args.single_quoted::()?.to_uppercase(); - let mut wordle_data = ctx.data.write().await; let mut wordle_map = wordle_data .get_mut::() - .expect("Failed to retrieve wordles map!").lock().await; - let mut wordle = wordle_map.games.get_mut(&(msg.channel_id, msg.author.id)); + .expect("Failed to retrieve wordle map!") + .lock() + .await; + let words_vector: Vec = wordle_map + .words + .words + .iter() + .map(|word| word.word.clone()) + .collect(); + let guess = args.single_quoted::()?.to_uppercase(); let mut string_response = String::from(""); - if wordle.is_none() { - string_response.push_str("To play the game write !start"); + + if guess.len() != DEFAULT_SIZE || !guess.chars().all(char::is_alphabetic) { + string_response.push_str("Guess word must contain 5 letters without numbers"); + } else if !words_vector.contains(&guess) { + string_response.push_str("Guess word is not in word list"); } else { - let mut wordle = wordle.unwrap(); - wordle.guesses += 1; - if guess.eq(&wordle.word) { - string_response.push_str("You won! 🎉"); - wordle_map.games.remove(&(msg.channel_id, msg.author.id)); - } else if wordle.guesses == GUESSES { - string_response.push_str("You ran out of guesses!\nThe word was: "); - string_response.push_str(wordle.word.as_str()); - wordle_map.games.remove(&(msg.channel_id, msg.author.id)); + let wordle = wordle_map.games.get_mut(&(msg.channel_id, msg.author.id)); + + if wordle.is_none() { + string_response.push_str("To play the game type !start"); } else { - string_response.push_str("Guess again!"); + let mut wordle = wordle.unwrap(); + wordle.guesses += 1; + + /* Check if the guess was correct or if the person ran out of guesses. + If not, add guess to the list and display all guesses. */ + if guess.eq(&wordle.word) { + string_response.push_str("You won! 🎉"); + wordle_map.games.remove(&(msg.channel_id, msg.author.id)); + } else if wordle.guesses == GUESSES { + string_response.push_str("You ran out of guesses!\nThe word was: "); + string_response.push_str(wordle.word.as_str()); + wordle_map.games.remove(&(msg.channel_id, msg.author.id)); + } else { + wordle.add_fields(guess); + wordle.display_game(&mut string_response); + string_response.push_str("Guess again!"); + } } } @@ -120,15 +149,18 @@ struct Public; async fn main() { let _ = Config::new().save(); let config = Config::load().unwrap(); - let mut client = ClientBuilder::new(config.token(), GatewayIntents::GUILD_MESSAGES.union(GatewayIntents::MESSAGE_CONTENT)) - .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!"); + let mut client = ClientBuilder::new( + config.token(), + GatewayIntents::GUILD_MESSAGES.union(GatewayIntents::MESSAGE_CONTENT), + ) + .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) } From 3bebc1ac21fe6f5dd1e42db9d6c80be940438b88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Tue, 31 May 2022 00:50:14 +0200 Subject: [PATCH 23/51] Debug + format --- src/wordle.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 4 deletions(-) diff --git a/src/wordle.rs b/src/wordle.rs index 1b68bad..0d5fb8f 100644 --- a/src/wordle.rs +++ b/src/wordle.rs @@ -1,19 +1,33 @@ use std::collections::HashMap; -pub enum Result {Green, Yellow, Black} -pub static DEFAULT_SIZE: u32 = 5; +pub enum Result { + Green, + Yellow, + Red, +} +pub static DEFAULT_SIZE: usize = 5; pub static GUESSES: u32 = 6; +pub static GREEN_SQUARE: &str = ":green_square: "; +pub static YELLOW_SQUARE: &str = ":yellow_square: "; +pub static RED_SQUARE: &str = ":red_square: "; +/* Struct representing a single char in guess word. */ pub struct Field { pub letter: char, - pub square: Result + 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 fields: HashMap>, } impl Wordle { @@ -24,4 +38,49 @@ impl Wordle { fields: HashMap::new(), } } + + 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 String) { + 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.push_str(&format!("{} ", field.letter)); + } + string_response.push('\n'); + + /* Displays different colored squares depending on square value in Field. */ + for field in vec_fields { + match field.square { + Result::Red => { + string_response.push_str(RED_SQUARE); + } + Result::Yellow => { + string_response.push_str(YELLOW_SQUARE); + } + Result::Green => { + string_response.push_str(GREEN_SQUARE); + } + } + } + string_response.push('\n'); + } + } + } } From c83e045b8956182052ec8b6d228a207227102ba6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Tue, 31 May 2022 00:50:43 +0200 Subject: [PATCH 24/51] Debug + format --- src/words.rs | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/words.rs b/src/words.rs index 9c3e0ac..142cad0 100644 --- a/src/words.rs +++ b/src/words.rs @@ -1,47 +1,41 @@ -use std::process::Output; -use serde::Deserialize; use bracket_random::prelude::RandomNumberGenerator; -use serenity::futures::future::MaybeDone::Future; -use serenity::futures::{StreamExt, TryStreamExt}; -use tokio::task::spawn_blocking; +use serde::Deserialize; +/* Struct representing a single available word. */ #[derive(Deserialize)] pub struct Word { - word: String, + pub word: String, } +/* Struct representing all available words to guess. */ pub struct Words { - words: Vec, -} - -impl Word { - pub fn word(&self) -> String { - self.word.clone() - } + pub words: Vec, } impl Words { pub async fn new() -> Words { - let result = reqwest::get("https://raw.githubusercontent.com/mongodb-developer/bash-wordle/main/words.json") - .await; + 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 - .unwrap() + words: vec![Word { + word: String::from("empty"), + }], } } + Ok(response) => Words { + words: response.json().await.unwrap(), + }, } } pub fn generate_word(&self) -> &Word { let mut rng = RandomNumberGenerator::new(); - rng.random_slice_entry(&self.words).unwrap().clone() + rng.random_slice_entry(&self.words).unwrap() } } From a26db10d232eba1d416ab4b50aa46bab164b0e6e Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Tue, 31 May 2022 00:58:08 +0200 Subject: [PATCH 25/51] added config.ron --- config.ron | 4 ++++ src/config.rs | 2 +- src/main.rs | 1 - 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 config.ron diff --git a/config.ron b/config.ron new file mode 100644 index 0000000..bcb33a0 --- /dev/null +++ b/config.ron @@ -0,0 +1,4 @@ +( + token: "Token goes here", + prefix: "!", +) \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 4d60565..c150df5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,7 @@ mod private { pub const PREFIX: &str = "!"; pub const TOKEN: &str = - "OTc2MjI1OTg0ODYyODc5ODI0.G6YFle.YGmB0wvOvC_BgfqSNkRXOw4w75aUHPq1QKme0M"; + "The token goes here"; } use ron::{de, ser}; diff --git a/src/main.rs b/src/main.rs index 6eab356..17bb3f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -147,7 +147,6 @@ struct Public; #[tokio::main] async fn main() { - let _ = Config::new().save(); let config = Config::load().unwrap(); let mut client = ClientBuilder::new( config.token(), From 51a262abad54c7740346fc4c6896e23237c8acf3 Mon Sep 17 00:00:00 2001 From: mielnikk <93001127+mielnikk@users.noreply.github.com> Date: Tue, 31 May 2022 01:02:59 +0200 Subject: [PATCH 26/51] updated readme --- README.md | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 16d7824..da9be88 100644 --- a/README.md +++ b/README.md @@ -5,17 +5,24 @@ - Julia Podrażka (gr 4, @julia-podrazka na githubie) ## Opis -Plan jest taki, że napiszemy bota do Discorda umożliwiającego interaktywne granie w pewne gry. Generalnie muszą to być gry, które łatwo można przedstawić w tekstowym formacie. - +Bot do Discorda umożliwiający granie w Wordle. ## Funkcjonalność -- przeprowadzanie rozgrywki -- może jakieś rankingi dla ludzi na serwerze?? +Gra w Wordle na czacie. Każdy użytkownik może rozpocząć instancję gry dla siebie, przy czym może mieć maksymalnie jedną aktywną rozgrywkę w obrębie jednego kanału. + +## Użycie +Token do bota osadzonego na swoim serwerze należy umieścić w pliku `config.ron`. -## Propozycja podziału na części -W pierwszej części Wordle. +Następnie odpalić bota za pomocą +``` +cargo run +``` -W drugiej części dodamy więcej gier i ewentualnie coś jeszcze. +## Plany na drugą część +- dodawanie zestawu customowych emoji na każdym serwerze, na którym się pojawi bot +- wybór długości słowa, które należy zgadnąć +- opcja "poddania się" poprzez dodanie reakcji białej flagi - bot wyświetlałby wtedy słowo oraz jego definicję zdobytą za pomocą API jakiegoś słownika +- ??? ## Biblioteki -Jesteśmy otwarte na sugestie. +W głównej mierze Serenity, pojawiły się również Tokio i Serde. From cea86d0df2575576f9e64b8ce4f9c84656b961f6 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Tue, 31 May 2022 01:07:07 +0200 Subject: [PATCH 27/51] fixed loading config in main.rs --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index 17bb3f2..6eab356 100644 --- a/src/main.rs +++ b/src/main.rs @@ -147,6 +147,7 @@ struct Public; #[tokio::main] async fn main() { + let _ = Config::new().save(); let config = Config::load().unwrap(); let mut client = ClientBuilder::new( config.token(), From a33c3396eb5ebba6fbe7df21587c814012631f31 Mon Sep 17 00:00:00 2001 From: mielnikk <93001127+mielnikk@users.noreply.github.com> Date: Tue, 31 May 2022 01:09:20 +0200 Subject: [PATCH 28/51] updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index da9be88..7671735 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Bot do Discorda umożliwiający granie w Wordle. Gra w Wordle na czacie. Każdy użytkownik może rozpocząć instancję gry dla siebie, przy czym może mieć maksymalnie jedną aktywną rozgrywkę w obrębie jednego kanału. ## Użycie -Token do bota osadzonego na swoim serwerze należy umieścić w pliku `config.ron`. +Token do bota osadzonego na swoim serwerze należy umieścić odpowiednim structcie w `config.rs`. Następnie odpalić bota za pomocą ``` From 6a92ac97427ad4767342cc7ddbe4004c626fa0d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Tue, 31 May 2022 12:03:14 +0200 Subject: [PATCH 29/51] Added string builder + cleanup --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 15610c4..701c054 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ 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" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } [dependencies.serenity] git = "https://github.com/serenity-rs/serenity" From d809333f63680710ba1cd9e6d2342796399328b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Tue, 31 May 2022 12:03:57 +0200 Subject: [PATCH 30/51] Added string builder + cleanup --- src/words.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/words.rs b/src/words.rs index 142cad0..30caaa9 100644 --- a/src/words.rs +++ b/src/words.rs @@ -14,6 +14,7 @@ pub struct Words { 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", ) @@ -24,18 +25,19 @@ impl Words { println!("Error fetching data: {}", why); Words { words: vec![Word { - word: String::from("empty"), + word: String::from("EMPTY"), }], } } Ok(response) => Words { - words: response.json().await.unwrap(), + 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).unwrap() + rng.random_slice_entry(&self.words) + .expect("Error getting random word") } } From 24f7ed32aa1dbda267e3fda8cbc855eac71206c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Tue, 31 May 2022 12:04:50 +0200 Subject: [PATCH 31/51] Added string builder + cleanup --- src/wordle.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/wordle.rs b/src/wordle.rs index 0d5fb8f..da4d29a 100644 --- a/src/wordle.rs +++ b/src/wordle.rs @@ -1,10 +1,12 @@ use std::collections::HashMap; +use string_builder::Builder; pub enum Result { Green, Yellow, Red, } + pub static DEFAULT_SIZE: usize = 5; pub static GUESSES: u32 = 6; pub static GREEN_SQUARE: &str = ":green_square: "; @@ -39,6 +41,8 @@ impl Wordle { } } + /* 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() { @@ -55,31 +59,31 @@ impl Wordle { self.fields.insert(self.guesses, field_vec); } - pub fn display_game(&self, string_response: &mut String) { + 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.push_str(&format!("{} ", field.letter)); + string_response.append(format!("{} ", field.letter)); } - string_response.push('\n'); + 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.push_str(RED_SQUARE); + string_response.append(RED_SQUARE); } Result::Yellow => { - string_response.push_str(YELLOW_SQUARE); + string_response.append(YELLOW_SQUARE); } Result::Green => { - string_response.push_str(GREEN_SQUARE); + string_response.append(GREEN_SQUARE); } } } - string_response.push('\n'); + string_response.append('\n'); } } } From 69975a400efa9ac76f50540997cd219ef0147a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Tue, 31 May 2022 12:06:55 +0200 Subject: [PATCH 32/51] Added string builder + cleanup --- src/main.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6eab356..e0db9e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,11 +14,13 @@ use serenity::{ }; use std::collections::HashMap; use std::sync::Arc; +use string_builder::Builder; use tokio::sync::Mutex; use wordle::Wordle; pub static HELLO_MSG: &str = "Hello, I'm a Wordle Bot"; +/* Structure to share data across server. */ struct ServerKey; impl TypeMapKey for ServerKey { @@ -101,18 +103,19 @@ async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { .map(|word| word.word.clone()) .collect(); + /* Word comparison is case insensitive. */ let guess = args.single_quoted::()?.to_uppercase(); - let mut string_response = String::from(""); + let mut string_response = Builder::default(); if guess.len() != DEFAULT_SIZE || !guess.chars().all(char::is_alphabetic) { - string_response.push_str("Guess word must contain 5 letters without numbers"); + string_response.append("Guess word must contain 5 letters without numbers"); } else if !words_vector.contains(&guess) { - string_response.push_str("Guess word is not in word list"); + string_response.append("Guess word is not in word list"); } else { let wordle = wordle_map.games.get_mut(&(msg.channel_id, msg.author.id)); if wordle.is_none() { - string_response.push_str("To play the game type !start"); + string_response.append("To play the game type !start"); } else { let mut wordle = wordle.unwrap(); wordle.guesses += 1; @@ -120,21 +123,25 @@ async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { /* Check if the guess was correct or if the person ran out of guesses. If not, add guess to the list and display all guesses. */ if guess.eq(&wordle.word) { - string_response.push_str("You won! 🎉"); + string_response.append("You won! 🎉"); wordle_map.games.remove(&(msg.channel_id, msg.author.id)); } else if wordle.guesses == GUESSES { - string_response.push_str("You ran out of guesses!\nThe word was: "); - string_response.push_str(wordle.word.as_str()); + string_response.append("You ran out of guesses!\nThe word was: "); + string_response.append(wordle.word.as_str()); wordle_map.games.remove(&(msg.channel_id, msg.author.id)); } else { wordle.add_fields(guess); wordle.display_game(&mut string_response); - string_response.push_str("Guess again!"); + string_response.append("Guess again!"); } } } - if let Err(why) = msg.channel_id.say(&ctx.http, &string_response).await { + if let Err(why) = msg + .channel_id + .say(&ctx.http, &string_response.string().unwrap()) + .await + { println!("Error sending message: {}", why); } Ok(()) @@ -161,6 +168,7 @@ async fn main() { .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) } From 12a17cf8c946e24b050053afa095bd112b4632d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Fri, 17 Jun 2022 19:55:10 +0200 Subject: [PATCH 33/51] Added game with multiple players --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 701c054..8bf4b1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ 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" From a1150d2997ea6f0e8074388007ea1b2a8aae8474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Fri, 17 Jun 2022 19:55:44 +0200 Subject: [PATCH 34/51] Added game with multiple players --- src/main.rs | 268 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 218 insertions(+), 50 deletions(-) diff --git a/src/main.rs b/src/main.rs index e0db9e5..a063ae9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ mod words; use crate::wordle::{DEFAULT_SIZE, GUESSES}; use crate::words::Words; use config::Config; +use const_format::formatcp; use serenity::{ client::ClientBuilder, framework::standard::{macros::command, macros::group, Args, CommandResult, StandardFramework}, @@ -14,11 +15,45 @@ use serenity::{ }; use std::collections::HashMap; use std::sync::Arc; +use std::time::SystemTime; +use std::vec::Vec; use string_builder::Builder; -use tokio::sync::Mutex; +use tokio::sync::{Mutex, MutexGuard}; use wordle::Wordle; -pub static HELLO_MSG: &str = "Hello, I'm a Wordle Bot"; +/* 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 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 guess the word as you are not in a group!"; +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!\nThe word was: "; +pub const YOUR_GUESSES_MSG: &str = " your guesses: \n"; +pub const GUESS_AGAIN: &str = "Guess again!"; + +/* 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; @@ -27,10 +62,14 @@ impl TypeMapKey for ServerKey { type Value = Arc>; } -/* Contains information on all instances of Wordle that have been started and -all available words to guess. */ +/* 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>, + games: HashMap<(ChannelId, UserId), (Wordle, SystemTime)>, + /* Takes value: one if there is at least one solo play or + number of players in a group if there is a group play. */ + max_people_playing: usize, + joined_people: Vec, words: Words, } @@ -38,12 +77,14 @@ impl ServerMap { pub async fn new() -> ServerMap { ServerMap { games: HashMap::new(), + max_people_playing: 1, + joined_people: Vec::new(), words: Words::new().await, } } } -async fn send_embed_message(ctx: &Context, msg: &Message, message: &str) { +async fn send_embed_message(ctx: &Context, msg: &Message, message: &str) -> CommandResult { if let Err(why) = msg .channel_id .send_message(ctx, |m| { @@ -53,39 +94,135 @@ async fn send_embed_message(ctx: &Context, msg: &Message, message: &str) { { println!("Error sending the help message: {}", why); } + Ok(()) } #[command] async fn help(ctx: &Context, msg: &Message) -> CommandResult { - send_embed_message(ctx, msg, &format!("Type `!start` to start the game.\n**Rules:**\nYou have {} tries to guess a {}-letter word.\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.", GUESSES, DEFAULT_SIZE)).await; - Ok(()) + 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) -> CommandResult { +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!"); - let wordle = Wordle::new(wordle_map.lock().await.words.generate_word().word.clone()); + check_ended_games(&mut wordle_map.lock().await); - send_embed_message( - ctx, - msg, - "Game started! Take a guess using `!guess [Your guess]`.", - ) - .await; - wordle_map + /* 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 - .games - .insert((msg.channel_id, msg.author.id), wordle); - Ok(()) + .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)); + wordle_map.joined_people.clear(); + wordle_map.max_people_playing = 1; } #[command] @@ -96,6 +233,33 @@ async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { .expect("Failed to retrieve wordle map!") .lock() .await; + + check_ended_games(&mut wordle_map); + + let mut author = msg.author.id; + let mut is_group = false; + + /* Check if a person can guess if there is a group game. */ + if wordle_map.max_people_playing > 1 { + is_group = true; + 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; + } else if !check_channel(&wordle_map, msg) { + return send_embed_message(ctx, msg, GUESS_WRONG_CHANNEL_MSG).await; + } else 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 @@ -108,33 +272,37 @@ async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let mut string_response = Builder::default(); if guess.len() != DEFAULT_SIZE || !guess.chars().all(char::is_alphabetic) { - string_response.append("Guess word must contain 5 letters without numbers"); - } else if !words_vector.contains(&guess) { - string_response.append("Guess word is not in word list"); + 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; + + /* Check if the guess was correct or if the person ran out of guesses. + If not, add guess to the list and display all guesses. */ + if guess.eq(&wordle.word) { + string_response.append(WON_MSG); + clean_game(&mut wordle_map, msg, author); + } else if wordle.guesses == GUESSES { + string_response.append(TOO_MANY_GUESSES_MSG); + string_response.append(wordle.word.as_str()); + clean_game(&mut wordle_map, msg, author); } else { - let wordle = wordle_map.games.get_mut(&(msg.channel_id, msg.author.id)); - - if wordle.is_none() { - string_response.append("To play the game type !start"); - } else { - let mut wordle = wordle.unwrap(); - wordle.guesses += 1; - - /* Check if the guess was correct or if the person ran out of guesses. - If not, add guess to the list and display all guesses. */ - if guess.eq(&wordle.word) { - string_response.append("You won! 🎉"); - wordle_map.games.remove(&(msg.channel_id, msg.author.id)); - } else if wordle.guesses == GUESSES { - string_response.append("You ran out of guesses!\nThe word was: "); - string_response.append(wordle.word.as_str()); - wordle_map.games.remove(&(msg.channel_id, msg.author.id)); - } else { - wordle.add_fields(guess); - wordle.display_game(&mut string_response); - string_response.append("Guess again!"); - } + wordle.add_fields(guess); + if !is_group { + string_response.append(msg.author.name.to_string()); + string_response.append(YOUR_GUESSES_MSG); } + wordle.display_game(&mut string_response); + string_response.append(GUESS_AGAIN); } if let Err(why) = msg @@ -149,7 +317,7 @@ async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { /* Declaration of a set of available commands. */ #[group("public")] -#[commands(start, guess, help)] +#[commands(start, guess, help, join)] struct Public; #[tokio::main] From 5faa9038015975b4fc29eab9b2e5b4905cbb06e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Fri, 17 Jun 2022 19:56:47 +0200 Subject: [PATCH 35/51] Added game with multiple players --- src/wordle.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/wordle.rs b/src/wordle.rs index da4d29a..1cc6f98 100644 --- a/src/wordle.rs +++ b/src/wordle.rs @@ -7,11 +7,11 @@ pub enum Result { Red, } -pub static DEFAULT_SIZE: usize = 5; -pub static GUESSES: u32 = 6; -pub static GREEN_SQUARE: &str = ":green_square: "; -pub static YELLOW_SQUARE: &str = ":yellow_square: "; -pub static RED_SQUARE: &str = ":red_square: "; +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 { From 179b34fbdd69e6a319c07ef9add5d9931ff1e994 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sat, 18 Jun 2022 19:19:27 +0200 Subject: [PATCH 36/51] adding a white flag reaction to bot messages; saving last message id of each game --- src/main.rs | 52 +++++++++++++++++++++++++++++++++++++++++---------- src/wordle.rs | 3 +++ 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/src/main.rs b/src/main.rs index a063ae9..f1d5c30 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,8 @@ use crate::wordle::{DEFAULT_SIZE, GUESSES}; use crate::words::Words; use config::Config; use const_format::formatcp; +use serenity::futures::TryFutureExt; +use serenity::http::Http; use serenity::{ client::ClientBuilder, framework::standard::{macros::command, macros::group, Args, CommandResult, StandardFramework}, @@ -225,6 +227,27 @@ fn clean_game(wordle_map: &mut MutexGuard<'_, ServerMap>, msg: &Message, author: wordle_map.max_people_playing = 1; } +/* Sends the contents of message_builder to a channel. */ +async fn send_message( + http: &Http, + channel: &ChannelId, + message_builder: Builder, +) -> Result { + channel.say(http, message_builder.string().unwrap()).await +} + +/* Adds a white flag reaction under a message. + * The message is supposed to display the current state of the game. */ +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); + } +} + #[command] async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let mut wordle_data = ctx.data.write().await; @@ -286,16 +309,24 @@ async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let mut wordle = &mut wordle.unwrap().0; wordle.guesses += 1; - /* Check if the guess was correct or if the person ran out of guesses. - If not, add guess to the list and display all guesses. */ + /* 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(&ctx.http, &msg.channel_id, string_response).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. */ string_response.append(TOO_MANY_GUESSES_MSG); string_response.append(wordle.word.as_str()); + if let Err(why) = send_message(&ctx.http, &msg.channel_id, string_response).await { + println!("Error sending the message: {}", why); + } clean_game(&mut wordle_map, msg, author); } else { + /* Other cases. */ wordle.add_fields(guess); if !is_group { string_response.append(msg.author.name.to_string()); @@ -303,14 +334,15 @@ async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { } wordle.display_game(&mut string_response); string_response.append(GUESS_AGAIN); - } - - if let Err(why) = msg - .channel_id - .say(&ctx.http, &string_response.string().unwrap()) - .await - { - println!("Error sending message: {}", why); + if let Err(why) = send_message(&ctx.http, &msg.channel_id, string_response) + .and_then(|message| async move { + react_to_message(&ctx.http, &message, wordle).await; + Ok(()) + }) + .await + { + println!("Error sending the message: {}", why); + } } Ok(()) } diff --git a/src/wordle.rs b/src/wordle.rs index 1cc6f98..720f40b 100644 --- a/src/wordle.rs +++ b/src/wordle.rs @@ -1,3 +1,4 @@ +use serenity::model::id::MessageId; use std::collections::HashMap; use string_builder::Builder; @@ -30,6 +31,7 @@ pub struct Wordle { pub word: String, pub guesses: u32, pub fields: HashMap>, + pub last_message_id: Option, } impl Wordle { @@ -38,6 +40,7 @@ impl Wordle { word, guesses: 0, fields: HashMap::new(), + last_message_id: None, } } From b16ef1696ee30a303ae780b7ac842c868e5b9b91 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sat, 18 Jun 2022 19:46:56 +0200 Subject: [PATCH 37/51] separated the messages module --- src/main.rs | 65 ++----------------------------------------- src/messages.rs | 74 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 63 deletions(-) create mode 100644 src/messages.rs diff --git a/src/main.rs b/src/main.rs index f1d5c30..0aee985 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,13 @@ mod config; mod wordle; mod words; +mod messages; use crate::wordle::{DEFAULT_SIZE, GUESSES}; use crate::words::Words; +use crate::messages::*; use config::Config; -use const_format::formatcp; use serenity::futures::TryFutureExt; -use serenity::http::Http; use serenity::{ client::ClientBuilder, framework::standard::{macros::command, macros::group, Args, CommandResult, StandardFramework}, @@ -23,35 +23,6 @@ use string_builder::Builder; use tokio::sync::{Mutex, MutexGuard}; use wordle::Wordle; -/* 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 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 guess the word as you are not in a group!"; -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!\nThe word was: "; -pub const YOUR_GUESSES_MSG: &str = " your guesses: \n"; -pub const GUESS_AGAIN: &str = "Guess again!"; /* Every solo player has 5 minutes to complete game. Group players have 5 minutes to join a game and another 5 minutes to play. */ @@ -86,18 +57,6 @@ impl ServerMap { } } -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(()) -} #[command] async fn help(ctx: &Context, msg: &Message) -> CommandResult { @@ -227,26 +186,6 @@ fn clean_game(wordle_map: &mut MutexGuard<'_, ServerMap>, msg: &Message, author: wordle_map.max_people_playing = 1; } -/* Sends the contents of message_builder to a channel. */ -async fn send_message( - http: &Http, - channel: &ChannelId, - message_builder: Builder, -) -> Result { - channel.say(http, message_builder.string().unwrap()).await -} - -/* Adds a white flag reaction under a message. - * The message is supposed to display the current state of the game. */ -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); - } -} #[command] async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { diff --git a/src/messages.rs b/src/messages.rs new file mode 100644 index 0000000..df89b97 --- /dev/null +++ b/src/messages.rs @@ -0,0 +1,74 @@ +use const_format::formatcp; +use serenity::framework::standard::CommandResult; +use serenity::http::Http; +use serenity::model::prelude::{ChannelId, Message, ReactionType}; +use serenity::prelude::{Context, SerenityError}; +use string_builder::Builder; +use crate::Wordle; +use crate::wordle::{DEFAULT_SIZE, GUESSES}; + + +/* 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 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 guess the word as you are not in a group!"; +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!\nThe word was: "; +pub const YOUR_GUESSES_MSG: &str = " your guesses: \n"; +pub const GUESS_AGAIN: &str = "Guess again!"; + + +/* Sends the contents of message_builder to a channel. */ +pub async fn send_message( + http: &Http, + channel: &ChannelId, + message_builder: Builder, +) -> Result { + channel.say(http, message_builder.string().unwrap()).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); + } +} + +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(()) +} From 16a79334b6c03fc07bc38ed0aade4427604b2a71 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sat, 18 Jun 2022 20:12:14 +0200 Subject: [PATCH 38/51] added a give up command --- src/main.rs | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0aee985..051f0dd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ mod config; +mod messages; mod wordle; mod words; -mod messages; +use crate::messages::*; use crate::wordle::{DEFAULT_SIZE, GUESSES}; use crate::words::Words; -use crate::messages::*; use config::Config; use serenity::futures::TryFutureExt; use serenity::{ @@ -23,7 +23,6 @@ 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; @@ -57,7 +56,6 @@ impl ServerMap { } } - #[command] async fn help(ctx: &Context, msg: &Message) -> CommandResult { send_embed_message(ctx, msg, HELP_MSG).await @@ -186,6 +184,41 @@ fn clean_game(wordle_map: &mut MutexGuard<'_, ServerMap>, msg: &Message, author: wordle_map.max_people_playing = 1; } +#[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; + } else 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.get_mut(&(msg.channel_id, author)); + if wordle.is_none() { + return send_embed_message(ctx, msg, START_PLAYING_MSG).await; + } + + let wordle = &mut wordle.unwrap().0; + let mut string_response = Builder::default(); + string_response.append("Your word was: "); + string_response.append(wordle.word.as_str()); + if let Err(why) = send_message(&ctx.http, &msg.channel_id, string_response).await { + println!("Error sending the message: {}", why); + } + clean_game(&mut wordle_map, msg, author); + + Ok(()) +} #[command] async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { @@ -288,7 +321,7 @@ async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { /* Declaration of a set of available commands. */ #[group("public")] -#[commands(start, guess, help, join)] +#[commands(start, guess, help, join, giveup)] struct Public; #[tokio::main] From f271c1c524f88694088da1abd8e870e799f1619f Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sun, 19 Jun 2022 13:18:42 +0200 Subject: [PATCH 39/51] fetching a word definition from a dictionary --- src/main.rs | 66 ++++++++++++++++++++++++++++++++++++++++--------- src/messages.rs | 59 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 110 insertions(+), 15 deletions(-) diff --git a/src/main.rs b/src/main.rs index 051f0dd..c6c541c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ 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::*, @@ -56,6 +57,54 @@ impl ServerMap { } } +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; + + /* Ignoring if a non-player reacts to the message. */ + if wordle_map.max_people_playing > 1 && !wordle_map.joined_people.contains(&user) { + return; + } + /* 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() { + if player != user { + return; + } + send_wordle_solution(wordle, &_add_reaction.channel_id, &_ctx.http).await; + wordle_map + .games + .retain(|_, (w, _)| w.last_message_id != Some(_add_reaction.message_id)); + wordle_map.joined_people.clear(); + wordle_map.max_people_playing = 1; + } + } +} + #[command] async fn help(ctx: &Context, msg: &Message) -> CommandResult { send_embed_message(ctx, msg, HELP_MSG).await @@ -203,20 +252,14 @@ async fn giveup(ctx: &Context, msg: &Message) -> CommandResult { author = wordle_map.joined_people[0]; } - let wordle = wordle_map.games.get_mut(&(msg.channel_id, author)); + let wordle = wordle_map.games.remove(&(msg.channel_id, author)); if wordle.is_none() { return send_embed_message(ctx, msg, START_PLAYING_MSG).await; } - - let wordle = &mut wordle.unwrap().0; - let mut string_response = Builder::default(); - string_response.append("Your word was: "); - string_response.append(wordle.word.as_str()); - if let Err(why) = send_message(&ctx.http, &msg.channel_id, string_response).await { - println!("Error sending the message: {}", why); - } - clean_game(&mut wordle_map, msg, author); - + wordle_map.joined_people.clear(); + wordle_map.max_people_playing = 1; + let wordle = &wordle.unwrap().0; + send_wordle_solution(wordle, &msg.channel_id, &ctx.http).await; Ok(()) } @@ -332,6 +375,7 @@ async fn main() { config.token(), GatewayIntents::GUILD_MESSAGES.union(GatewayIntents::MESSAGE_CONTENT), ) + .event_handler(Handler) .framework( StandardFramework::new() .configure(|c| c.with_whitespace(true).prefix(config.prefix())) diff --git a/src/messages.rs b/src/messages.rs index df89b97..5a4d273 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -1,12 +1,13 @@ +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}; use serenity::prelude::{Context, SerenityError}; -use string_builder::Builder; -use crate::Wordle; -use crate::wordle::{DEFAULT_SIZE, GUESSES}; +use string_builder::Builder; /* Messages send by bot. */ pub const HELLO_MSG: &str = "Hello, I'm a Wordle Bot"; @@ -37,7 +38,7 @@ pub const WON_MSG: &str = "You won! 🎉"; pub const TOO_MANY_GUESSES_MSG: &str = "You ran out of guesses!\nThe word was: "; 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_message( @@ -60,6 +61,56 @@ pub async fn react_to_message(http: &Http, message: &Message, wordle: &mut Wordl } } +/* 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, http: &Http) { + let definition = get_definition(wordle.word.as_str()).await; + + if let Err(why) = channel + .send_message(http, |m| { + m.content("Your word was:") + .embed(|e| e.title(wordle.word.as_str()).description(definition)) + }) + .await + { + println!("Error sending the message: {}", why); + } +} + pub async fn send_embed_message(ctx: &Context, msg: &Message, message: &str) -> CommandResult { if let Err(why) = msg .channel_id From 99a91c6b1bfd7ff421c2fbfc1932d8447ae12b65 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sun, 19 Jun 2022 13:37:45 +0200 Subject: [PATCH 40/51] bot responds to a white flag reaction --- config.ron | 2 +- src/main.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config.ron b/config.ron index bcb33a0..dc3882e 100644 --- a/config.ron +++ b/config.ron @@ -1,4 +1,4 @@ ( - token: "Token goes here", + token: "OTc2MjI1OTg0ODYyODc5ODI0.G6YFle.YGmB0wvOvC_BgfqSNkRXOw4w75aUHPq1QKme0M", prefix: "!", ) \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c6c541c..eed04f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -373,7 +373,9 @@ async fn main() { let config = Config::load().unwrap(); let mut client = ClientBuilder::new( config.token(), - GatewayIntents::GUILD_MESSAGES.union(GatewayIntents::MESSAGE_CONTENT), + GatewayIntents::GUILD_MESSAGES + .union(GatewayIntents::MESSAGE_CONTENT) + .union(GatewayIntents::GUILD_MESSAGE_REACTIONS), ) .event_handler(Handler) .framework( From 2f875e8346681b99e92c42e1a96ae0e6bf86f31a Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sun, 19 Jun 2022 13:43:10 +0200 Subject: [PATCH 41/51] security fix --- .gitignore | 1 + config.ron | 2 +- src/config.rs | 2 +- src/main.rs | 1 - 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index ea8c4bf..1b55109 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +config.ron \ No newline at end of file diff --git a/config.ron b/config.ron index dc3882e..d246646 100644 --- a/config.ron +++ b/config.ron @@ -1,4 +1,4 @@ ( - token: "OTc2MjI1OTg0ODYyODc5ODI0.G6YFle.YGmB0wvOvC_BgfqSNkRXOw4w75aUHPq1QKme0M", + token: "Your token goes here", prefix: "!", ) \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index c150df5..e696de8 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,7 +1,7 @@ mod private { pub const PREFIX: &str = "!"; pub const TOKEN: &str = - "The token goes here"; + ""; } use ron::{de, ser}; diff --git a/src/main.rs b/src/main.rs index eed04f8..088e88a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -369,7 +369,6 @@ struct Public; #[tokio::main] async fn main() { - let _ = Config::new().save(); let config = Config::load().unwrap(); let mut client = ClientBuilder::new( config.token(), From c52d6060cc604852ad0500fbc2406384188dd5a0 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sun, 19 Jun 2022 15:48:41 +0200 Subject: [PATCH 42/51] player mentions, reformatted sending messages --- src/main.rs | 66 ++++++++++++++++++++++++------------- src/messages.rs | 87 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 116 insertions(+), 37 deletions(-) diff --git a/src/main.rs b/src/main.rs index 088e88a..ceaa84d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,6 +56,15 @@ impl ServerMap { } } } +/* 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] + } +} struct Handler; @@ -79,11 +88,14 @@ impl EventHandler for Handler { .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 wordle_map.max_people_playing > 1 && !wordle_map.joined_people.contains(&user) { + 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. */ @@ -92,10 +104,12 @@ impl EventHandler for Handler { .iter_mut() .filter(|(_, (w, _))| w.last_message_id == Some(_add_reaction.message_id)); if let Some((&(_, player), (wordle, _))) = coll.next() { - if player != user { + /* Somebody else reacted to a player's game. */ + if single_player && player != user { return; } - send_wordle_solution(wordle, &_add_reaction.channel_id, &_ctx.http).await; + 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)); @@ -256,10 +270,17 @@ async fn giveup(ctx: &Context, msg: &Message) -> CommandResult { if wordle.is_none() { return send_embed_message(ctx, msg, START_PLAYING_MSG).await; } + wordle_map.joined_people.clear(); wordle_map.max_people_playing = 1; let wordle = &wordle.unwrap().0; - send_wordle_solution(wordle, &msg.channel_id, &ctx.http).await; + send_wordle_solution( + wordle, + &msg.channel_id, + get_players(&mut wordle_map, author), + &ctx.http, + ) + .await; Ok(()) } @@ -275,11 +296,10 @@ async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { check_ended_games(&mut wordle_map); let mut author = msg.author.id; - let mut is_group = false; + 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 { - is_group = true; if wordle_map.joined_people.len() != wordle_map.max_people_playing { return send_embed_message( ctx, @@ -328,33 +348,35 @@ async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { if guess.eq(&wordle.word) { /* The guess was entirely correct */ string_response.append(WON_MSG); - if let Err(why) = send_message(&ctx.http, &msg.channel_id, string_response).await { + 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. */ - string_response.append(TOO_MANY_GUESSES_MSG); - string_response.append(wordle.word.as_str()); - if let Err(why) = send_message(&ctx.http, &msg.channel_id, string_response).await { + 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 !is_group { - string_response.append(msg.author.name.to_string()); - string_response.append(YOUR_GUESSES_MSG); - } - wordle.display_game(&mut string_response); - string_response.append(GUESS_AGAIN); - if let Err(why) = send_message(&ctx.http, &msg.channel_id, string_response) - .and_then(|message| async move { - react_to_message(&ctx.http, &message, wordle).await; - Ok(()) - }) - .await + 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); } diff --git a/src/messages.rs b/src/messages.rs index 5a4d273..2a15fab 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -4,8 +4,9 @@ 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}; +use serenity::model::prelude::{ChannelId, Message, ReactionType, UserId}; use serenity::prelude::{Context, SerenityError}; +use serenity::utils::Colour; use string_builder::Builder; @@ -14,7 +15,9 @@ 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 Type `!start ` to start a game with friends.\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!"; @@ -34,14 +37,14 @@ pub const INCORRECT_GUESS_MSG: &str = "Guess word must contain 5 letters without 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!\nThe word was: "; +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_message( +pub async fn send_builder_contents( http: &Http, channel: &ChannelId, message_builder: Builder, @@ -49,6 +52,29 @@ pub async fn send_message( 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) { @@ -61,6 +87,23 @@ pub async fn react_to_message(http: &Http, message: &Message, wordle: &mut Wordl } } +/* 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. */ @@ -97,17 +140,31 @@ async fn get_definition(word: &str) -> String { /* 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, http: &Http) { - let definition = get_definition(wordle.word.as_str()).await; +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("Your word was:") - .embed(|e| e.title(wordle.word.as_str()).description(definition)) - }) - .await - { - println!("Error sending the message: {}", why); + 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 { } } From 7438bcea54ccba4ce422e5a8beb7a49cc413bfc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Sun, 26 Jun 2022 14:57:44 +0200 Subject: [PATCH 43/51] Small cleanup --- src/main.rs | 420 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 420 insertions(+) diff --git a/src/main.rs b/src/main.rs index ceaa84d..3d9395c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,16 @@ 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::{mod config; +mod messages; +mod wordle; +mod words; + use crate::messages::*; use crate::wordle::{DEFAULT_SIZE, GUESSES}; use crate::words::Words; @@ -24,6 +34,416 @@ 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) + } +} + + 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; From ed9f77cfc53681e3ba0a98ec5866e70335aa7c08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Sun, 26 Jun 2022 14:58:22 +0200 Subject: [PATCH 44/51] Small cleanup --- src/wordle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wordle.rs b/src/wordle.rs index 720f40b..9654fa6 100644 --- a/src/wordle.rs +++ b/src/wordle.rs @@ -45,7 +45,7 @@ impl Wordle { } /* Saves guess word as Fields with corresponding color describing if char - matches the chars in a word to guess. */ + * 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() { From 4f020cde55facc4034e30ab7d4797d3e481f2a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Sun, 26 Jun 2022 15:05:22 +0200 Subject: [PATCH 45/51] Update README.md --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 7671735..afb0668 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,10 @@ Następnie odpalić bota za pomocą cargo run ``` -## Plany na drugą część -- dodawanie zestawu customowych emoji na każdym serwerze, na którym się pojawi bot -- wybór długości słowa, które należy zgadnąć -- opcja "poddania się" poprzez dodanie reakcji białej flagi - bot wyświetlałby wtedy słowo oraz jego definicję zdobytą za pomocą API jakiegoś słownika -- ??? +## Zmiany w drugiej części +- możliwość grania w grupie +- opcja "poddania się" poprzez dodanie reakcji białej flagi - bot wyświetla wtedy słowo oraz jego definicję zdobytą za pomocą API słownika +- pięciominutowe ograniczenie czasowe na każdą grę ## Biblioteki W głównej mierze Serenity, pojawiły się również Tokio i Serde. From 9443b3241fe7b3617d3b596fbc3247cccf056574 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Sun, 26 Jun 2022 17:22:22 +0200 Subject: [PATCH 46/51] Removed duplicated code --- src/main.rs | 404 ---------------------------------------------------- 1 file changed, 404 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3d9395c..89a7d70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -405,410 +405,6 @@ async fn guess(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { #[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) - } -} - - 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 - 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] - } -} - -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)); - wordle_map.joined_people.clear(); - wordle_map.max_people_playing = 1; - } - } -} - -#[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)); - wordle_map.joined_people.clear(); - wordle_map.max_people_playing = 1; -} - -#[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; - } else 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; - } - - wordle_map.joined_people.clear(); - wordle_map.max_people_playing = 1; - 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; - } else if !check_channel(&wordle_map, msg) { - return send_embed_message(ctx, msg, GUESS_WRONG_CHANNEL_MSG).await; - } else 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(); From 9e22f0423797fa15226d2cf6c7309d0b23a42288 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sun, 26 Jun 2022 17:25:39 +0200 Subject: [PATCH 47/51] removed double imports --- src/main.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 89a7d70..0f6116c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,16 +8,7 @@ use crate::wordle::{DEFAULT_SIZE, GUESSES}; use crate::words::Words; use config::Config; use serenity::futures::TryFutureExt; -use serenity::{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, From 12d773ae893c9e41b16012772297146216be2d60 Mon Sep 17 00:00:00 2001 From: Katarzyna Mielnik Date: Sun, 26 Jun 2022 17:25:56 +0200 Subject: [PATCH 48/51] config cleanup --- src/config.rs | 38 +------------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/src/config.rs b/src/config.rs index e696de8..ecfb927 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,12 +1,6 @@ -mod private { - pub const PREFIX: &str = "!"; - pub const TOKEN: &str = - ""; -} -use ron::{de, ser}; +use ron::{de}; use serde::{Deserialize, Serialize}; -use std::io::Write; #[derive(Debug, Serialize, Deserialize)] pub struct Config { @@ -15,13 +9,6 @@ pub struct Config { } impl Config { - pub fn new() -> Self { - Config { - token: String::from(private::TOKEN), - prefix: String::from(private::PREFIX), - } - } - pub fn token(&self) -> &str { self.token.as_str() } @@ -30,29 +17,6 @@ impl Config { self.prefix.as_str() } - /* Saves the configuration data into 'config.ron'. */ - pub fn save(&self) -> std::io::Result<()> { - let data = Config { - token: String::from(private::TOKEN), - prefix: String::from(private::PREFIX), - }; - - let pretty = ser::PrettyConfig::new() - .depth_limit(2) - .separate_tuple_members(true) - .enumerate_arrays(true); - - let s = ser::to_string_pretty(&data, pretty).expect("Serialization failed!"); - - let mut file = std::fs::File::create("config.ron")?; - if let Err(why) = write!(file, "{}", s) { - println!("Failed writing to file: {}", why); - } else { - println!("Write operation succeeded!"); - } - Ok(()) - } - /* 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")); From 3a2ab73158b5dca40e56acd0499b163a766efc45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Sun, 26 Jun 2022 23:12:00 +0200 Subject: [PATCH 49/51] Fixed give-up message --- src/messages.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/messages.rs b/src/messages.rs index 2a15fab..a2116cf 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -32,7 +32,7 @@ pub const WRONG_CHANNEL_MSG: &str = "If you want to join your friends type `!joi 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 guess the word as you are not in a group!"; +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`! \ From b020fb39667e51cdd143f6f12a8965c285a36be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Wed, 26 Apr 2023 02:49:13 +0200 Subject: [PATCH 50/51] Update README.md --- README.md | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index afb0668..9d31b0d 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,22 @@ # Discord Game Bot -## Autorzy -- Katarzyna Mielnik (gr 4, @mielnikk na githubie) -- Julia Podrażka (gr 4, @julia-podrazka na githubie) +## Autors +- Katarzyna Mielnik @mielnikk +- Julia Podrażka @julia-podrazka -## Opis -Bot do Discorda umożliwiający granie w Wordle. +## Description +Discord bot for playing Wordle. +Each user can start a game to play alone or in a group. +If you want to give up a game, click on a white flag in reactions under your last guess - then the game will be finished, and you will see a correct word with its definition. +Each game has a timer - you only have 5 minutes to guess the word. -## Funkcjonalność -Gra w Wordle na czacie. Każdy użytkownik może rozpocząć instancję gry dla siebie, przy czym może mieć maksymalnie jedną aktywną rozgrywkę w obrębie jednego kanału. +## Installing a bot +First, put your bot's token in the correct struct in `config.rs`. -## Użycie -Token do bota osadzonego na swoim serwerze należy umieścić odpowiednim structcie w `config.rs`. - -Następnie odpalić bota za pomocą +Then run command: ``` cargo run ``` -## Zmiany w drugiej części -- możliwość grania w grupie -- opcja "poddania się" poprzez dodanie reakcji białej flagi - bot wyświetla wtedy słowo oraz jego definicję zdobytą za pomocą API słownika -- pięciominutowe ograniczenie czasowe na każdą grę - -## Biblioteki -W głównej mierze Serenity, pojawiły się również Tokio i Serde. +## Libraries +Our program uses primarily Serenity, as well as Tokio and Serde. From 4e11dc40f772a4ce077ebdb2d770ed5e545759d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julia=20Podra=C5=BCka?= <73885839+julia-podrazka@users.noreply.github.com> Date: Thu, 27 Apr 2023 20:14:36 +0200 Subject: [PATCH 51/51] Update README.md --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9d31b0d..56f0e3d 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,14 @@ ## Description Discord bot for playing Wordle. + Each user can start a game to play alone or in a group. -If you want to give up a game, click on a white flag in reactions under your last guess - then the game will be finished, and you will see a correct word with its definition. -Each game has a timer - you only have 5 minutes to guess the word. + +Each channel can have multiple solo games at a time but only one multiplayer game. + +There are only 5-letter words. + +Each game has a timer - you only have 5 minutes to guess the word, and you are given a maximum of 6 guesses. ## Installing a bot First, put your bot's token in the correct struct in `config.rs`. @@ -18,5 +23,42 @@ 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.