diff --git a/Dockerfile b/Dockerfile index 39e81e1..48ce025 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.11.4-alpine +FROM python:3.12.5-alpine WORKDIR /bot @@ -7,4 +7,4 @@ RUN pip3 install -r requirements.txt COPY . . -CMD python3 main.py +CMD ["python3", "main.py"] diff --git a/Pipfile b/Pipfile index 8a81186..4d24de5 100644 --- a/Pipfile +++ b/Pipfile @@ -12,4 +12,4 @@ aiofiles = "*" [dev-packages] [requires] -python_version = "3.11" +python_version = "3.12" diff --git a/Pipfile.lock b/Pipfile.lock index e8abc17..6377c10 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "c6221750d946cb12d5bad57057ccb5831239a0df2a3665bf3409e7e3784bbb6b" + "sha256": "85100b31f9b060d619f216374f02463fb6280b9012078b454b9bd56980c14234" }, "pipfile-spec": 6, "requires": { - "python_version": "3.11" + "python_version": "3.12" }, "sources": [ { @@ -18,94 +18,117 @@ "default": { "aiofiles": { "hashes": [ - "sha256:19297512c647d4b27a2cf7c34caa7e405c0d60b5560618a29a9fe027b18b0107", - "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a" + "sha256:22a075c9e5a3810f0c2e48f3008c94d68c65d763b9b03857924c99e57355166c", + "sha256:b4ec55f4195e3eb5d7abd1bf7e061763e864dd4954231fb8539a0ef8bb8260e5" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==23.2.1" + "markers": "python_version >= '3.8'", + "version": "==24.1.0" + }, + "aiohappyeyeballs": { + "hashes": [ + "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2", + "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd" + ], + "markers": "python_version >= '3.8'", + "version": "==2.4.0" }, "aiohttp": { "hashes": [ - "sha256:02ab6006ec3c3463b528374c4cdce86434e7b89ad355e7bf29e2f16b46c7dd6f", - "sha256:04fa38875e53eb7e354ece1607b1d2fdee2d175ea4e4d745f6ec9f751fe20c7c", - "sha256:0b0a6a36ed7e164c6df1e18ee47afbd1990ce47cb428739d6c99aaabfaf1b3af", - "sha256:0d406b01a9f5a7e232d1b0d161b40c05275ffbcbd772dc18c1d5a570961a1ca4", - "sha256:0e49b08eafa4f5707ecfb321ab9592717a319e37938e301d462f79b4e860c32a", - "sha256:0e7ba7ff228c0d9a2cd66194e90f2bca6e0abca810b786901a569c0de082f489", - "sha256:11cb254e397a82efb1805d12561e80124928e04e9c4483587ce7390b3866d213", - "sha256:11ff168d752cb41e8492817e10fb4f85828f6a0142b9726a30c27c35a1835f01", - "sha256:176df045597e674fa950bf5ae536be85699e04cea68fa3a616cf75e413737eb5", - "sha256:219a16763dc0294842188ac8a12262b5671817042b35d45e44fd0a697d8c8361", - "sha256:22698f01ff5653fe66d16ffb7658f582a0ac084d7da1323e39fd9eab326a1f26", - "sha256:237533179d9747080bcaad4d02083ce295c0d2eab3e9e8ce103411a4312991a0", - "sha256:289ba9ae8e88d0ba16062ecf02dd730b34186ea3b1e7489046fc338bdc3361c4", - "sha256:2c59e0076ea31c08553e868cec02d22191c086f00b44610f8ab7363a11a5d9d8", - "sha256:2c9376e2b09895c8ca8b95362283365eb5c03bdc8428ade80a864160605715f1", - "sha256:3135713c5562731ee18f58d3ad1bf41e1d8883eb68b363f2ffde5b2ea4b84cc7", - "sha256:3b9c7426923bb7bd66d409da46c41e3fb40f5caf679da624439b9eba92043fa6", - "sha256:3c0266cd6f005e99f3f51e583012de2778e65af6b73860038b968a0a8888487a", - "sha256:41473de252e1797c2d2293804e389a6d6986ef37cbb4a25208de537ae32141dd", - "sha256:4831df72b053b1eed31eb00a2e1aff6896fb4485301d4ccb208cac264b648db4", - "sha256:49f0c1b3c2842556e5de35f122fc0f0b721334ceb6e78c3719693364d4af8499", - "sha256:4b4c452d0190c5a820d3f5c0f3cd8a28ace48c54053e24da9d6041bf81113183", - "sha256:4ee8caa925aebc1e64e98432d78ea8de67b2272252b0a931d2ac3bd876ad5544", - "sha256:500f1c59906cd142d452074f3811614be04819a38ae2b3239a48b82649c08821", - "sha256:5216b6082c624b55cfe79af5d538e499cd5f5b976820eac31951fb4325974501", - "sha256:54311eb54f3a0c45efb9ed0d0a8f43d1bc6060d773f6973efd90037a51cd0a3f", - "sha256:54631fb69a6e44b2ba522f7c22a6fb2667a02fd97d636048478db2fd8c4e98fe", - "sha256:565760d6812b8d78d416c3c7cfdf5362fbe0d0d25b82fed75d0d29e18d7fc30f", - "sha256:598db66eaf2e04aa0c8900a63b0101fdc5e6b8a7ddd805c56d86efb54eb66672", - "sha256:5c4fa235d534b3547184831c624c0b7c1e262cd1de847d95085ec94c16fddcd5", - "sha256:69985d50a2b6f709412d944ffb2e97d0be154ea90600b7a921f95a87d6f108a2", - "sha256:69da0f3ed3496808e8cbc5123a866c41c12c15baaaead96d256477edf168eb57", - "sha256:6c93b7c2e52061f0925c3382d5cb8980e40f91c989563d3d32ca280069fd6a87", - "sha256:70907533db712f7aa791effb38efa96f044ce3d4e850e2d7691abd759f4f0ae0", - "sha256:81b77f868814346662c96ab36b875d7814ebf82340d3284a31681085c051320f", - "sha256:82eefaf1a996060602f3cc1112d93ba8b201dbf5d8fd9611227de2003dddb3b7", - "sha256:85c3e3c9cb1d480e0b9a64c658cd66b3cfb8e721636ab8b0e746e2d79a7a9eed", - "sha256:8a22a34bc594d9d24621091d1b91511001a7eea91d6652ea495ce06e27381f70", - "sha256:8cef8710fb849d97c533f259103f09bac167a008d7131d7b2b0e3a33269185c0", - "sha256:8d44e7bf06b0c0a70a20f9100af9fcfd7f6d9d3913e37754c12d424179b4e48f", - "sha256:8d7f98fde213f74561be1d6d3fa353656197f75d4edfbb3d94c9eb9b0fc47f5d", - "sha256:8d8e4450e7fe24d86e86b23cc209e0023177b6d59502e33807b732d2deb6975f", - "sha256:8fc49a87ac269d4529da45871e2ffb6874e87779c3d0e2ccd813c0899221239d", - "sha256:90ec72d231169b4b8d6085be13023ece8fa9b1bb495e4398d847e25218e0f431", - "sha256:91c742ca59045dce7ba76cab6e223e41d2c70d79e82c284a96411f8645e2afff", - "sha256:9b05d33ff8e6b269e30a7957bd3244ffbce2a7a35a81b81c382629b80af1a8bf", - "sha256:9b05d5cbe9dafcdc733262c3a99ccf63d2f7ce02543620d2bd8db4d4f7a22f83", - "sha256:9c5857612c9813796960c00767645cb5da815af16dafb32d70c72a8390bbf690", - "sha256:a34086c5cc285be878622e0a6ab897a986a6e8bf5b67ecb377015f06ed316587", - "sha256:ab221850108a4a063c5b8a70f00dd7a1975e5a1713f87f4ab26a46e5feac5a0e", - "sha256:b796b44111f0cab6bbf66214186e44734b5baab949cb5fb56154142a92989aeb", - "sha256:b8c3a67eb87394386847d188996920f33b01b32155f0a94f36ca0e0c635bf3e3", - "sha256:bcb6532b9814ea7c5a6a3299747c49de30e84472fa72821b07f5a9818bce0f66", - "sha256:bcc0ea8d5b74a41b621ad4a13d96c36079c81628ccc0b30cfb1603e3dfa3a014", - "sha256:bea94403a21eb94c93386d559bce297381609153e418a3ffc7d6bf772f59cc35", - "sha256:bff7e2811814fa2271be95ab6e84c9436d027a0e59665de60edf44e529a42c1f", - "sha256:c72444d17777865734aa1a4d167794c34b63e5883abb90356a0364a28904e6c0", - "sha256:c7b5d5d64e2a14e35a9240b33b89389e0035e6de8dbb7ffa50d10d8b65c57449", - "sha256:c7e939f1ae428a86e4abbb9a7c4732bf4706048818dfd979e5e2839ce0159f23", - "sha256:c88a15f272a0ad3d7773cf3a37cc7b7d077cbfc8e331675cf1346e849d97a4e5", - "sha256:c9110c06eaaac7e1f5562caf481f18ccf8f6fdf4c3323feab28a93d34cc646bd", - "sha256:ca7ca5abfbfe8d39e653870fbe8d7710be7a857f8a8386fc9de1aae2e02ce7e4", - "sha256:cae4c0c2ca800c793cae07ef3d40794625471040a87e1ba392039639ad61ab5b", - "sha256:cdefe289681507187e375a5064c7599f52c40343a8701761c802c1853a504558", - "sha256:cf2a0ac0615842b849f40c4d7f304986a242f1e68286dbf3bd7a835e4f83acfd", - "sha256:cfeadf42840c1e870dc2042a232a8748e75a36b52d78968cda6736de55582766", - "sha256:d737e69d193dac7296365a6dcb73bbbf53bb760ab25a3727716bbd42022e8d7a", - "sha256:d7481f581251bb5558ba9f635db70908819caa221fc79ee52a7f58392778c636", - "sha256:df9cf74b9bc03d586fc53ba470828d7b77ce51b0582d1d0b5b2fb673c0baa32d", - "sha256:e1f80197f8b0b846a8d5cf7b7ec6084493950d0882cc5537fb7b96a69e3c8590", - "sha256:ecca113f19d5e74048c001934045a2b9368d77b0b17691d905af18bd1c21275e", - "sha256:ee2527134f95e106cc1653e9ac78846f3a2ec1004cf20ef4e02038035a74544d", - "sha256:f27fdaadce22f2ef950fc10dcdf8048407c3b42b73779e48a4e76b3c35bca26c", - "sha256:f694dc8a6a3112059258a725a4ebe9acac5fe62f11c77ac4dcf896edfa78ca28", - "sha256:f800164276eec54e0af5c99feb9494c295118fc10a11b997bbb1348ba1a52065", - "sha256:ffcd828e37dc219a72c9012ec44ad2e7e3066bec6ff3aaa19e7d435dbf4032ca" + "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277", + "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1", + "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe", + "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb", + "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca", + "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91", + "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972", + "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a", + "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3", + "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa", + "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77", + "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b", + "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8", + "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599", + "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc", + "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf", + "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511", + "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699", + "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487", + "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987", + "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff", + "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db", + "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022", + "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce", + "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a", + "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5", + "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7", + "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820", + "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf", + "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e", + "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf", + "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5", + "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6", + "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6", + "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91", + "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3", + "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a", + "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d", + "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088", + "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc", + "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f", + "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75", + "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471", + "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e", + "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697", + "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092", + "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69", + "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3", + "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32", + "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589", + "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178", + "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92", + "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2", + "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e", + "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058", + "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857", + "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1", + "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6", + "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22", + "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0", + "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b", + "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57", + "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f", + "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e", + "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16", + "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1", + "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f", + "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6", + "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04", + "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae", + "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d", + "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b", + "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f", + "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862", + "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689", + "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c", + "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683", + "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef", + "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f", + "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12", + "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73", + "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061", + "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072", + "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11", + "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691", + "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77", + "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385", + "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172", + "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569", + "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f", + "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5" ], "markers": "python_version >= '3.8'", - "version": "==3.9.1" + "version": "==3.10.5" }, "aiosignal": { "hashes": [ @@ -117,20 +140,20 @@ }, "aiosqlite": { "hashes": [ - "sha256:95ee77b91c8d2808bd08a59fbebf66270e9090c3d92ffbf260dc0db0b979577d", - "sha256:edba222e03453e094a3ce605db1b970c4b3376264e56f32e2a4959f948d66a96" + "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6", + "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==0.19.0" + "markers": "python_version >= '3.8'", + "version": "==0.20.0" }, "attrs": { "hashes": [ - "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30", - "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1" + "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", + "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2" ], "markers": "python_version >= '3.7'", - "version": "==23.2.0" + "version": "==24.2.0" }, "discord": { "hashes": [ @@ -142,11 +165,11 @@ }, "discord.py": { "hashes": [ - "sha256:4560f70f2eddba7e83370ecebd237ac09fbb4980dc66507482b0c0e5b8f76b9c", - "sha256:9da4679fc3cb10c64b388284700dc998663e0e57328283bbfcfc2525ec5960a6" + "sha256:b8af6711c70f7e62160bfbecb55be699b5cb69d007426759ab8ab06b1bd77d1d", + "sha256:d07cb2a223a185873a1d0ee78b9faa9597e45b3f6186df21a95cec1e9bcdc9a5" ], - "markers": "python_full_version >= '3.8.0'", - "version": "==2.3.2" + "markers": "python_version >= '3.8'", + "version": "==2.4.0" }, "frozenlist": { "hashes": [ @@ -233,152 +256,202 @@ }, "idna": { "hashes": [ - "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca", - "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f" + "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac", + "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603" ], - "markers": "python_version >= '3.5'", - "version": "==3.6" + "markers": "python_version >= '3.6'", + "version": "==3.8" }, "multidict": { "hashes": [ - "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9", - "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8", - "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03", - "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710", - "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161", - "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664", - "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569", - "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067", - "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313", - "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706", - "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2", - "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636", - "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49", - "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93", - "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603", - "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0", - "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60", - "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4", - "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e", - "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1", - "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60", - "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951", - "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc", - "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe", - "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95", - "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d", - "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8", - "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed", - "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2", - "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775", - "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87", - "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c", - "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2", - "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98", - "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3", - "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe", - "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78", - "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660", - "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176", - "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e", - "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988", - "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c", - "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c", - "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0", - "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449", - "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f", - "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde", - "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5", - "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d", - "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac", - "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a", - "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9", - "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca", - "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11", - "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35", - "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063", - "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b", - "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982", - "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258", - "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1", - "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52", - "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480", - "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7", - "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461", - "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d", - "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc", - "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779", - "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a", - "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547", - "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0", - "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171", - "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf", - "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d", - "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba" + "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556", + "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c", + "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29", + "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b", + "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8", + "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7", + "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd", + "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40", + "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6", + "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3", + "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c", + "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9", + "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5", + "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae", + "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442", + "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9", + "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc", + "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c", + "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea", + "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5", + "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50", + "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182", + "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453", + "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e", + "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600", + "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733", + "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda", + "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241", + "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461", + "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e", + "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e", + "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b", + "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e", + "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7", + "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386", + "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd", + "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9", + "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf", + "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee", + "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5", + "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a", + "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271", + "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54", + "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4", + "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496", + "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb", + "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319", + "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3", + "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f", + "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527", + "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed", + "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604", + "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef", + "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8", + "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5", + "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5", + "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626", + "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c", + "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d", + "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c", + "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc", + "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc", + "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b", + "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38", + "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450", + "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1", + "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f", + "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3", + "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755", + "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226", + "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a", + "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046", + "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf", + "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479", + "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e", + "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1", + "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a", + "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83", + "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929", + "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93", + "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a", + "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c", + "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44", + "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89", + "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba", + "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e", + "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da", + "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24", + "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423", + "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef" ], "markers": "python_version >= '3.7'", - "version": "==6.0.4" + "version": "==6.0.5" }, "pillow": { "hashes": [ - "sha256:00f438bb841382b15d7deb9a05cc946ee0f2c352653c7aa659e75e592f6fa17d", - "sha256:0248f86b3ea061e67817c47ecbe82c23f9dd5d5226200eb9090b3873d3ca32de", - "sha256:04f6f6149f266a100374ca3cc368b67fb27c4af9f1cc8cb6306d849dcdf12616", - "sha256:062a1610e3bc258bff2328ec43f34244fcec972ee0717200cb1425214fe5b839", - "sha256:0a026c188be3b443916179f5d04548092e253beb0c3e2ee0a4e2cdad72f66099", - "sha256:0f7c276c05a9767e877a0b4c5050c8bee6a6d960d7f0c11ebda6b99746068c2a", - "sha256:1a8413794b4ad9719346cd9306118450b7b00d9a15846451549314a58ac42219", - "sha256:1ab05f3db77e98f93964697c8efc49c7954b08dd61cff526b7f2531a22410106", - "sha256:1c3ac5423c8c1da5928aa12c6e258921956757d976405e9467c5f39d1d577a4b", - "sha256:1c41d960babf951e01a49c9746f92c5a7e0d939d1652d7ba30f6b3090f27e412", - "sha256:1fafabe50a6977ac70dfe829b2d5735fd54e190ab55259ec8aea4aaea412fa0b", - "sha256:1fb29c07478e6c06a46b867e43b0bcdb241b44cc52be9bc25ce5944eed4648e7", - "sha256:24fadc71218ad2b8ffe437b54876c9382b4a29e030a05a9879f615091f42ffc2", - "sha256:2cdc65a46e74514ce742c2013cd4a2d12e8553e3a2563c64879f7c7e4d28bce7", - "sha256:2ef6721c97894a7aa77723740a09547197533146fba8355e86d6d9a4a1056b14", - "sha256:3b834f4b16173e5b92ab6566f0473bfb09f939ba14b23b8da1f54fa63e4b623f", - "sha256:3d929a19f5469b3f4df33a3df2983db070ebb2088a1e145e18facbc28cae5b27", - "sha256:41f67248d92a5e0a2076d3517d8d4b1e41a97e2df10eb8f93106c89107f38b57", - "sha256:47e5bf85b80abc03be7455c95b6d6e4896a62f6541c1f2ce77a7d2bb832af262", - "sha256:4d0152565c6aa6ebbfb1e5d8624140a440f2b99bf7afaafbdbf6430426497f28", - "sha256:50d08cd0a2ecd2a8657bd3d82c71efd5a58edb04d9308185d66c3a5a5bed9610", - "sha256:61f1a9d247317fa08a308daaa8ee7b3f760ab1809ca2da14ecc88ae4257d6172", - "sha256:6932a7652464746fcb484f7fc3618e6503d2066d853f68a4bd97193a3996e273", - "sha256:7a7e3daa202beb61821c06d2517428e8e7c1aab08943e92ec9e5755c2fc9ba5e", - "sha256:7dbaa3c7de82ef37e7708521be41db5565004258ca76945ad74a8e998c30af8d", - "sha256:7df5608bc38bd37ef585ae9c38c9cd46d7c81498f086915b0f97255ea60c2818", - "sha256:806abdd8249ba3953c33742506fe414880bad78ac25cc9a9b1c6ae97bedd573f", - "sha256:883f216eac8712b83a63f41b76ddfb7b2afab1b74abbb413c5df6680f071a6b9", - "sha256:912e3812a1dbbc834da2b32299b124b5ddcb664ed354916fd1ed6f193f0e2d01", - "sha256:937bdc5a7f5343d1c97dc98149a0be7eb9704e937fe3dc7140e229ae4fc572a7", - "sha256:9882a7451c680c12f232a422730f986a1fcd808da0fd428f08b671237237d651", - "sha256:9a92109192b360634a4489c0c756364c0c3a2992906752165ecb50544c251312", - "sha256:9d7bc666bd8c5a4225e7ac71f2f9d12466ec555e89092728ea0f5c0c2422ea80", - "sha256:a5f63b5a68daedc54c7c3464508d8c12075e56dcfbd42f8c1bf40169061ae666", - "sha256:a646e48de237d860c36e0db37ecaecaa3619e6f3e9d5319e527ccbc8151df061", - "sha256:a89b8312d51715b510a4fe9fc13686283f376cfd5abca8cd1c65e4c76e21081b", - "sha256:a92386125e9ee90381c3369f57a2a50fa9e6aa8b1cf1d9c4b200d41a7dd8e992", - "sha256:ae88931f93214777c7a3aa0a8f92a683f83ecde27f65a45f95f22d289a69e593", - "sha256:afc8eef765d948543a4775f00b7b8c079b3321d6b675dde0d02afa2ee23000b4", - "sha256:b0eb01ca85b2361b09480784a7931fc648ed8b7836f01fb9241141b968feb1db", - "sha256:b1c25762197144e211efb5f4e8ad656f36c8d214d390585d1d21281f46d556ba", - "sha256:b4005fee46ed9be0b8fb42be0c20e79411533d1fd58edabebc0dd24626882cfd", - "sha256:b920e4d028f6442bea9a75b7491c063f0b9a3972520731ed26c83e254302eb1e", - "sha256:baada14941c83079bf84c037e2d8b7506ce201e92e3d2fa0d1303507a8538212", - "sha256:bb40c011447712d2e19cc261c82655f75f32cb724788df315ed992a4d65696bb", - "sha256:c0949b55eb607898e28eaccb525ab104b2d86542a85c74baf3a6dc24002edec2", - "sha256:c9aeea7b63edb7884b031a35305629a7593272b54f429a9869a4f63a1bf04c34", - "sha256:cfe96560c6ce2f4c07d6647af2d0f3c54cc33289894ebd88cfbb3bcd5391e256", - "sha256:d27b5997bdd2eb9fb199982bb7eb6164db0426904020dc38c10203187ae2ff2f", - "sha256:d921bc90b1defa55c9917ca6b6b71430e4286fc9e44c55ead78ca1a9f9eba5f2", - "sha256:e6bf8de6c36ed96c86ea3b6e1d5273c53f46ef518a062464cd7ef5dd2cf92e38", - "sha256:eaed6977fa73408b7b8a24e8b14e59e1668cfc0f4c40193ea7ced8e210adf996", - "sha256:fa1d323703cfdac2036af05191b969b910d8f115cf53093125e4058f62012c9a", - "sha256:fe1e26e1ffc38be097f0ba1d0d07fcade2bcfd1d023cda5b29935ae8052bd793" + "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885", + "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea", + "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df", + "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5", + "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c", + "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d", + "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd", + "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06", + "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908", + "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a", + "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be", + "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0", + "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b", + "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80", + "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a", + "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e", + "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9", + "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696", + "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b", + "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309", + "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e", + "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab", + "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d", + "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060", + "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d", + "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d", + "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4", + "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3", + "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6", + "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb", + "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94", + "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b", + "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496", + "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0", + "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319", + "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b", + "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856", + "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef", + "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680", + "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b", + "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42", + "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e", + "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597", + "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a", + "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8", + "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3", + "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736", + "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da", + "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126", + "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd", + "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5", + "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b", + "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026", + "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b", + "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc", + "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46", + "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2", + "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c", + "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe", + "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984", + "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a", + "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70", + "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca", + "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b", + "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91", + "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3", + "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84", + "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1", + "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5", + "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be", + "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f", + "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc", + "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9", + "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e", + "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141", + "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef", + "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22", + "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27", + "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e", + "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==10.1.0" + "version": "==10.4.0" + }, + "typing-extensions": { + "hashes": [ + "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", + "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8" + ], + "markers": "python_version >= '3.8'", + "version": "==4.12.2" }, "yarl": { "hashes": [ diff --git a/README.md b/README.md index 9328b5c..fc67908 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Codenames-bot -### Bot is down. More info: [#1][issue-1] + [![Invite to server](https://img.shields.io/badge/INVITE%20TO%20SERVER-555555?style=for-the-badge&logo=discord&logoWidth=32&logoColor=ffffff&labelColor=5865f2)](https://discord.com/api/oauth2/authorize?client_id=841776986246348851&permissions=274878015552&scope=bot%20applications.commands) diff --git a/bot.py b/bot.py index a8bce27..01800f8 100644 --- a/bot.py +++ b/bot.py @@ -26,22 +26,32 @@ async def setup_hook(self) -> None: await self.tree.set_translator(CodenamesTranslator()) for filename in filter(lambda fn: "cog" in fn, os.listdir("handlers")): - await self.load_extension(f"handlers.{filename[:-3]}") # removing ".py" at the end of the filename + await self.load_extension( + f"handlers.{filename[:-3]}" + ) # removing ".py" at the end of the filename async def get_prefix(bot: CodenamesBot, message: Message) -> Iterable[str]: if message.guild: - request = await bot.db.fetch("SELECT prefix FROM guilds WHERE id = ?", (message.guild.id,)) + request = await bot.db.fetch( + "SELECT prefix FROM guilds WHERE id = ?", (message.guild.id,) + ) if not request: # should not normally happen - await bot.db.exec_and_commit("INSERT INTO guilds VALUES (?, ?, ?)", (message.guild.id, "", "en")) + await bot.db.exec_and_commit( + "INSERT INTO guilds VALUES (?, ?, ?)", (message.guild.id, "", "en") + ) else: - request = await bot.db.fetch("SELECT prefix FROM players WHERE id = ?", (message.author.id,)) + request = await bot.db.fetch( + "SELECT prefix FROM players WHERE id = ?", (message.author.id,) + ) - if not request: # if the user sends a text command to the bot as the first use in DMs + if ( + not request + ): # if the user sends a text command to the bot as the first use in DMs await bot.db.exec_and_commit( "INSERT INTO players VALUES (?, strftime('%d/%m/%Y','now'), ?, ?, ?, ?, ?, ?)", - (message.author.id, "", "en", 0, 0, 0, 0) + (message.author.id, "", "en", 0, 0, 0, 0), ) prefix = request[0] if request else "" @@ -59,7 +69,7 @@ def main() -> None: help_command=None, strip_after_prefix=True, intents=CodenamesBot.custom_intents(), - owner_ids=ADMINS + owner_ids=ADMINS, ) token = os.environ.get("TOKEN") diff --git a/handlers/checks.py b/handlers/checks.py index 1132553..7ccdf86 100644 --- a/handlers/checks.py +++ b/handlers/checks.py @@ -8,7 +8,11 @@ def is_moderator(): async def predicate(ctx: Context | Interaction) -> bool: user = ctx.author if isinstance(ctx, Context) else ctx.user - if not ctx.guild or ctx.channel.permissions_for(user).manage_messages or user in ADMINS: + if ( + not ctx.guild + or ctx.channel.permissions_for(user).manage_messages + or user in ADMINS + ): return True if isinstance(ctx, Interaction): diff --git a/handlers/event_cog.py b/handlers/event_cog.py index b88b550..9b491a6 100644 --- a/handlers/event_cog.py +++ b/handlers/event_cog.py @@ -1,7 +1,14 @@ from discord import Message, Guild, Activity, ActivityType from discord.ext.commands import ( - Context, Command, Cog, - CommandNotFound, BadArgument, MemberNotFound, NoPrivateMessage, MissingPermissions, NotOwner + Context, + Command, + Cog, + CommandNotFound, + BadArgument, + MemberNotFound, + NoPrivateMessage, + MissingPermissions, + NotOwner, ) from bot import CodenamesBot @@ -14,7 +21,9 @@ def __init__(self, bot: CodenamesBot) -> None: @Cog.listener() async def on_ready(self) -> None: - await self.bot.change_presence(activity=Activity(type=ActivityType.watching, name="codenames.me")) + await self.bot.change_presence( + activity=Activity(type=ActivityType.watching, name="codenames.me") + ) @Cog.listener() async def on_message(self, message: Message) -> None: @@ -45,11 +54,15 @@ async def on_command_error(self, ctx: Context, error: Exception) -> None: @Cog.listener() async def on_guild_join(self, guild: Guild) -> None: - await self.bot.db.exec_and_commit("INSERT INTO guilds VALUES (?, ?, ?)", (guild.id, "", "en")) + await self.bot.db.exec_and_commit( + "INSERT INTO guilds VALUES (?, ?, ?)", (guild.id, "", "en") + ) @Cog.listener() async def on_guild_remove(self, guild: Guild) -> None: - await self.bot.db.exec_and_commit("DELETE FROM guilds WHERE id = ?", (guild.id,)) + await self.bot.db.exec_and_commit( + "DELETE FROM guilds WHERE id = ?", (guild.id,) + ) async def setup(bot: CodenamesBot) -> None: diff --git a/handlers/game_cog.py b/handlers/game_cog.py index f5b8750..7bd5c8d 100644 --- a/handlers/game_cog.py +++ b/handlers/game_cog.py @@ -14,7 +14,15 @@ from misc.util import send_error, send_fields, most_count_reaction_emojis, pros_and_cons import misc.generation as gen from misc.constants import ( - EMPTY, ALPHABET, REACTION_ALPHABET, REACTION_R, REACTION_NUMBERS, flags_lang_rev, dictionaries, Paths, Colors + EMPTY, + ALPHABET, + REACTION_ALPHABET, + REACTION_R, + REACTION_NUMBERS, + flags_lang_rev, + dictionaries, + Paths, + Colors, ) @@ -31,17 +39,23 @@ async def game(self, ctx: Context) -> None: embed=Embed( title=loc.commands.game.registration, description=f"{loc.commands.game.registration_started}\n\n" - f"_{loc.commands.game.registration_instructions}_", - color=Colors.purple + f"_{loc.commands.game.registration_instructions}_", + color=Colors.purple, ), - view=RegistrationView(loc, self.start, ctx.author.id) + view=RegistrationView(loc, self.start, ctx.author.id), ) @hybrid_command(aliases=("stat", "ss", "st"), description=locale_str("stats")) - @describe(member=locale_str("stats_member_param"), show=locale_str("stats_show_param")) - async def stats(self, ctx: Context, member: Member | None = None, show: bool = False) -> None: + @describe( + member=locale_str("stats_member_param"), show=locale_str("stats_show_param") + ) + async def stats( + self, ctx: Context, member: Member | None = None, show: bool = False + ) -> None: player = member or ctx.author - name = f"**{player.global_name if player.global_name else player.display_name}**" + name = ( + f"**{player.global_name if player.global_name else player.display_name}**" + ) loc = await self.bot.db.localization(ctx) @@ -49,12 +63,14 @@ async def stats(self, ctx: Context, member: Member | None = None, show: bool = F game_master_embed = Embed( title=loc.commands.stats.smbs_stats.format(name), description=loc.commands.stats.egg_game_master_desc, - color=Colors.purple + color=Colors.purple, ) if ctx.interaction: # noinspection PyUnresolvedReferences - await ctx.interaction.response.send_message(embed=game_master_embed, ephemeral=not show) + await ctx.interaction.response.send_message( + embed=game_master_embed, ephemeral=not show + ) else: await ctx.reply(embed=game_master_embed) @@ -62,10 +78,12 @@ async def stats(self, ctx: Context, member: Member | None = None, show: bool = F info = await self.bot.db.fetch( "SELECT date, games, games_cap, wins, wins_cap FROM players WHERE id = ?", - (player.id,) + (player.id,), ) if not info: - await send_error(ctx, loc.errors.title, loc.errors.never_played.format(name)) + await send_error( + ctx, loc.errors.title, loc.errors.never_played.format(name) + ) return # noinspection PyTupleAssignmentBalance @@ -79,98 +97,117 @@ async def stats(self, ctx: Context, member: Member | None = None, show: bool = F stats_embed = Embed( title=loc.commands.stats.smbs_stats.format(name), description=loc.commands.stats.playing_since.format(f"**{date}**"), - color=Colors.purple + color=Colors.purple, ) stats_embed.add_field( name=loc.commands.stats.total, value=f"{loc.commands.stats.games_played}: **{games}**\n{loc.commands.stats.games_won}: **{wins}**" - f"\n{loc.commands.stats.winrate}: **{winrate}**" + f"\n{loc.commands.stats.winrate}: **{winrate}**", ) stats_embed.add_field( name=loc.commands.stats.team, value=f"{loc.commands.stats.games_played}: **{games_tm}**\n{loc.commands.stats.games_won}: **{wins_team}**" - f"\n{loc.commands.stats.winrate}: **{winrate_team}**" + f"\n{loc.commands.stats.winrate}: **{winrate_team}**", ) stats_embed.add_field( name=loc.commands.stats.captain, value=f"{loc.commands.stats.games_played}: **{games_cap}**\n{loc.commands.stats.games_won}: **{wins_cap}**" - f"\n{loc.commands.stats.winrate}: **{winrate_cap}**" + f"\n{loc.commands.stats.winrate}: **{winrate_cap}**", ) - stats_embed.add_field( - name=EMPTY, - value=loc.commands.stats.note, - inline=False - ) + stats_embed.add_field(name=EMPTY, value=loc.commands.stats.note, inline=False) stats_embed.set_thumbnail(url=player.display_avatar) if ctx.interaction: # noinspection PyUnresolvedReferences - await ctx.interaction.response.send_message(embed=stats_embed, ephemeral=not show) + await ctx.interaction.response.send_message( + embed=stats_embed, ephemeral=not show + ) else: await ctx.reply(embed=stats_embed) - - async def start(self, interaction: Interaction, team1: list[User], team2: list[User]) -> None: + async def start( + self, interaction: Interaction, team1: list[User], team2: list[User] + ) -> None: game_uuid = str(get_uuid()) - channel = self.bot.get_partial_messageable(interaction.channel_id, guild_id=interaction.guild_id) + channel = self.bot.get_partial_messageable( + interaction.channel_id, guild_id=interaction.guild_id + ) loc = await self.bot.db.localization(interaction) # Dictionary selection - language_msg = await channel.send(embed=Embed( - title=loc.commands.start.lang_selection_title, - color=Colors.purple - )) + language_msg = await channel.send( + embed=Embed( + title=loc.commands.start.lang_selection_title, color=Colors.purple + ) + ) for flag in flags_lang_rev.keys(): await language_msg.add_reaction(flag) dict_language: str = flags_lang_rev[ - (await self.bot.wait_for( - "reaction_add", - check=lambda reaction, user: reaction.message == language_msg and - user.id == interaction.user.id and - reaction.me - ))[0].emoji + ( + await self.bot.wait_for( + "reaction_add", + check=lambda reaction, user: reaction.message == language_msg + and user.id == interaction.user.id + and reaction.me, + ) + )[0].emoji ] dict_msg_desc = map( lambda num, value: f"**{num}** - {value}", - range(1, 10), dictionaries[dict_language].values() + range(1, 10), + dictionaries[dict_language].values(), + ) + dict_msg = await channel.send( + embed=Embed( + title=loc.commands.start.dict_selection_title, + description="{}\n\n{}".format( + "\n".join(dict_msg_desc), loc.commands.start.dict_selection_desc + ), + color=Colors.purple, + ) ) - dict_msg = await channel.send(embed=Embed( - title=loc.commands.start.dict_selection_title, - description="{}\n\n{}".format("\n".join(dict_msg_desc), loc.commands.start.dict_selection_desc), - color=Colors.purple - )) - for r_num in REACTION_NUMBERS[:len(dictionaries[dict_language])]: + for r_num in REACTION_NUMBERS[: len(dictionaries[dict_language])]: await dict_msg.add_reaction(r_num) await asyncio.sleep(15) new_dict_msg = await channel.fetch_message(dict_msg.id) emojis = await most_count_reaction_emojis(new_dict_msg, team1 + team2) - potential_dicts = map(lambda e: tuple(dictionaries[dict_language].keys())[REACTION_NUMBERS.index(e)], emojis) + potential_dicts = map( + lambda e: tuple(dictionaries[dict_language].keys())[ + REACTION_NUMBERS.index(e) + ], + emojis, + ) game_dict_name = random.choice(tuple(potential_dicts)) - await channel.send(embed=Embed( - title=loc.commands.start.dict_selected, - description=dictionaries[dict_language][game_dict_name], - color=Colors.purple - )) + await channel.send( + embed=Embed( + title=loc.commands.start.dict_selected, + description=dictionaries[dict_language][game_dict_name], + color=Colors.purple, + ) + ) # Captains selection cap_selection_list = map( - lambda letter, player: f"**{letter}** - {player.mention}", - ALPHABET, team1 + lambda letter, player: f"**{letter}** - {player.mention}", ALPHABET, team1 + ) + cap_msg = await channel.send( + embed=Embed( + title=loc.commands.start.cap_selection_title.format(loc.game.red), + description=loc.commands.start.cap_selection_desc.format( + "\n".join(cap_selection_list) + ), + color=Colors.red, + ) ) - cap_msg = await channel.send(embed=Embed( - title=loc.commands.start.cap_selection_title.format(loc.game.red), - description=loc.commands.start.cap_selection_desc.format("\n".join(cap_selection_list)), - color=Colors.red - )) await cap_msg.add_reaction(REACTION_R) - for r_letter in REACTION_ALPHABET[:len(team1)]: + for r_letter in REACTION_ALPHABET[: len(team1)]: await cap_msg.add_reaction(r_letter) await asyncio.sleep(15) @@ -186,24 +223,31 @@ async def start(self, interaction: Interaction, team1: list[User], team2: list[U team1_pl = team1.copy() team1_pl.remove(team1_cap) - await channel.send(embed=Embed( - title=loc.commands.start.cap_selected_title.format(loc.game.red), - description=loc.commands.start.cap_selected_desc.format(team1_cap.mention), - color=Colors.red - )) + await channel.send( + embed=Embed( + title=loc.commands.start.cap_selected_title.format(loc.game.red), + description=loc.commands.start.cap_selected_desc.format( + team1_cap.mention + ), + color=Colors.red, + ) + ) # The same code for team2_cap cap_selection_list = map( - lambda letter, player: f"**{letter}** - {player.mention}", - ALPHABET, team2 + lambda letter, player: f"**{letter}** - {player.mention}", ALPHABET, team2 + ) + cap_msg = await channel.send( + embed=Embed( + title=loc.commands.start.cap_selection_title.format(loc.game.blue), + description=loc.commands.start.cap_selection_desc.format( + "\n".join(cap_selection_list) + ), + color=Colors.blue, + ) ) - cap_msg = await channel.send(embed=Embed( - title=loc.commands.start.cap_selection_title.format(loc.game.blue), - description=loc.commands.start.cap_selection_desc.format("\n".join(cap_selection_list)), - color=Colors.blue - )) await cap_msg.add_reaction(REACTION_R) - for r_letter in REACTION_ALPHABET[:len(team2)]: + for r_letter in REACTION_ALPHABET[: len(team2)]: await cap_msg.add_reaction(r_letter) await asyncio.sleep(15) @@ -219,66 +263,80 @@ async def start(self, interaction: Interaction, team1: list[User], team2: list[U team2_pl = team2.copy() team2_pl.remove(team2_cap) - await channel.send(embed=Embed( - title=loc.commands.start.cap_selected_title.format(loc.game.blue), - description=loc.commands.start.cap_selected_desc.format(team2_cap.mention), - color=Colors.blue - )) - + await channel.send( + embed=Embed( + title=loc.commands.start.cap_selected_title.format(loc.game.blue), + description=loc.commands.start.cap_selected_desc.format( + team2_cap.mention + ), + color=Colors.blue, + ) + ) - await channel.send(embed=Embed( - title=loc.game.start_announcement, - color=Colors.purple - )) + await channel.send( + embed=Embed(title=loc.game.start_announcement, color=Colors.purple) + ) # Notifying everyone in direct messages - await team1_cap.send(embed=Embed( - title=loc.game.start_notification_title, - description=loc.game.start_notification_desc_cap.format( - loc.game.red, - "\n".join(map(lambda p: p.mention, team1_pl)) - ), - color=Colors.red - )) + await team1_cap.send( + embed=Embed( + title=loc.game.start_notification_title, + description=loc.game.start_notification_desc_cap.format( + loc.game.red, "\n".join(map(lambda p: p.mention, team1_pl)) + ), + color=Colors.red, + ) + ) for player in team1_pl: team1_pl_without = team1_pl.copy() - team1_pl_without.remove(player) # Team1 player list without recipient of the message - await player.send(embed=Embed( + team1_pl_without.remove( + player + ) # Team1 player list without recipient of the message + await player.send( + embed=Embed( + title=loc.game.start_notification_title, + description=loc.game.start_notification_desc_pl.format( + loc.game.red, + team1_cap.mention, + "\n".join(map(lambda p: p.mention, team1_pl_without)), + ), + color=Colors.red, + ) + ) + + await team2_cap.send( + embed=Embed( title=loc.game.start_notification_title, - description=loc.game.start_notification_desc_pl.format( - loc.game.red, - team1_cap.mention, - "\n".join(map(lambda p: p.mention, team1_pl_without)) + description=loc.game.start_notification_desc_cap.format( + loc.game.blue, "\n".join(map(lambda p: p.mention, team2_pl)) ), - color=Colors.red - )) - - await team2_cap.send(embed=Embed( - title=loc.game.start_notification_title, - description=loc.game.start_notification_desc_cap.format( - loc.game.blue, - "\n".join(map(lambda p: p.mention, team2_pl)) - ), - color=Colors.blue - )) + color=Colors.blue, + ) + ) for player in team2_pl: team2_pl_without = team2_pl.copy() - team2_pl_without.remove(player) # Team2 player list without recipient of the message - await player.send(embed=Embed( - title=loc.game.start_notification_title, - description=loc.game.start_notification_desc_pl.format( - loc.game.blue, - team2_cap.mention, - "\n".join(map(lambda p: p.mention, team2_pl_without)) - ), - color=Colors.blue - )) + team2_pl_without.remove( + player + ) # Team2 player list without recipient of the message + await player.send( + embed=Embed( + title=loc.game.start_notification_title, + description=loc.game.start_notification_desc_pl.format( + loc.game.blue, + team2_cap.mention, + "\n".join(map(lambda p: p.mention, team2_pl_without)), + ), + color=Colors.blue, + ) + ) team1_words, team2_words, endgame_word, no_team_words = await gen.words( lang=dict_language, dict_name=game_dict_name ) opened_words = [] - available_words = list(team1_words + team2_words + (endgame_word,) + no_team_words) # endgame_word is single + available_words = list( + team1_words + team2_words + (endgame_word,) + no_team_words + ) # endgame_word is single order = available_words.copy() # Has to be a list random.shuffle(order) order = tuple(order) @@ -307,63 +365,83 @@ async def start(self, interaction: Interaction, team1: list[User], team2: list[U first_round = True send_field_to_caps = True while game_running: - gen.field(team1_words, team2_words, endgame_word, no_team_words, opened_words, order, game_uuid) - await send_fields(game_uuid, channel, current_cap, other_cap, send_field_to_caps) + gen.field( + team1_words, + team2_words, + endgame_word, + no_team_words, + opened_words, + order, + game_uuid, + ) + await send_fields( + game_uuid, channel, current_cap, other_cap, send_field_to_caps + ) send_field_to_caps = True if first_round: - shutil.copy( - Paths.cap_img(game_uuid), - Paths.cap_img_init(game_uuid) - ) + shutil.copy(Paths.cap_img(game_uuid), Paths.cap_img_init(game_uuid)) first_round = False - await channel.send(embed=Embed( - title=loc.game.waiting_title, - description=loc.game.waiting_desc_cap.format(current_color), - color=Colors.red if current_color == loc.game.red else Colors.blue - )) - await current_cap.send(embed=Embed( - title=loc.game.cap_move_request_title, - description=loc.game.cap_move_request_desc.format( - "animal 3" if dict_language == "en" else "животное 3" - ), - color=Colors.red if current_color == loc.game.red else Colors.blue - )) + await channel.send( + embed=Embed( + title=loc.game.waiting_title, + description=loc.game.waiting_desc_cap.format(current_color), + color=Colors.red if current_color == loc.game.red else Colors.blue, + ) + ) + await current_cap.send( + embed=Embed( + title=loc.game.cap_move_request_title, + description=loc.game.cap_move_request_desc.format( + "animal 3" if dict_language == "en" else "животное 3" + ), + color=Colors.red if current_color == loc.game.red else Colors.blue, + ) + ) move_msg = await self.bot.wait_for( "message", - check=lambda msg: msg.channel == current_cap.dm_channel and - re.fullmatch(r"\w+ \d+", msg.content) and - not msg.content.endswith(" 0") + check=lambda msg: msg.channel == current_cap.dm_channel + and re.fullmatch(r"\w+ \d+", msg.content) + and not msg.content.endswith(" 0"), ) move = move_msg.content word_count = int(move.split()[-1]) - await current_cap.send(embed=Embed( - title=loc.game.cap_move_accepted, - color=Colors.red if current_color == loc.game.red else Colors.blue - )) - await channel.send(embed=Embed( - title=loc.game.cap_move_notification_title.format(current_color), - description=loc.game.cap_move_notification_desc.format(move), - color=Colors.red if current_color == loc.game.red else Colors.blue - )) - - await channel.send(embed=Embed( - title=loc.game.waiting_title, - description=f"{loc.game.waiting_desc_pl.format(current_color)}\n\n{loc.game.pl_move_instructions}", - color=Colors.red if current_color == loc.game.red else Colors.blue - )) + await current_cap.send( + embed=Embed( + title=loc.game.cap_move_accepted, + color=Colors.red if current_color == loc.game.red else Colors.blue, + ) + ) + await channel.send( + embed=Embed( + title=loc.game.cap_move_notification_title.format(current_color), + description=loc.game.cap_move_notification_desc.format(move), + color=Colors.red if current_color == loc.game.red else Colors.blue, + ) + ) + + await channel.send( + embed=Embed( + title=loc.game.waiting_title, + description=f"{loc.game.waiting_desc_pl.format(current_color)}\n\n{loc.game.pl_move_instructions}", + color=Colors.red if current_color == loc.game.red else Colors.blue, + ) + ) while word_count >= 0: # >= because of the rule that the team can open one more word than their captain intended move_msg = await self.bot.wait_for( "message", check=lambda msg: ( - msg.channel.id == channel.id and - msg.author in current_pl and - (msg.content.lower() in available_words or msg.content == "0") - ) or (msg.content == "000" and msg.author in team1 + team2) + msg.channel.id == channel.id + and msg.author in current_pl + and ( + msg.content.lower() in available_words or msg.content == "0" + ) + ) + or (msg.content == "000" and msg.author in team1 + team2), ) move = move_msg.content.lower().replace("ё", "е") @@ -372,225 +450,365 @@ async def start(self, interaction: Interaction, team1: list[User], team2: list[U send_field_to_caps = False break if move == "000": - stop_msg = await move_msg.reply(embed=Embed( - title=loc.game.voting_for_stopping_title, - description=loc.game.voting_for_stopping_desc, - color=Colors.purple - )) + stop_msg = await move_msg.reply( + embed=Embed( + title=loc.game.voting_for_stopping_title, + description=loc.game.voting_for_stopping_desc, + color=Colors.purple, + ) + ) pros, cons = await pros_and_cons(stop_msg, 15, team1 + team2) if pros > cons: - await channel.send(embed=Embed( - title=loc.game.game_stopped_title, - description=loc.game.game_stopped_desc, - color=Colors.purple - )) + await channel.send( + embed=Embed( + title=loc.game.game_stopped_title, + description=loc.game.game_stopped_desc, + color=Colors.purple, + ) + ) game_running = False break else: - await channel.send(embed=Embed( - title=loc.game.game_continued_title, - description=loc.game.game_continued_desc, - color=Colors.purple - )) + await channel.send( + embed=Embed( + title=loc.game.game_continued_title, + description=loc.game.game_continued_desc, + color=Colors.purple, + ) + ) continue # No need to generate a new field or decrease word_count opened_words.append(move) available_words.remove(move) - gen.field(team1_words, team2_words, endgame_word, no_team_words, opened_words, order, game_uuid) + gen.field( + team1_words, + team2_words, + endgame_word, + no_team_words, + opened_words, + order, + game_uuid, + ) if move in no_team_words: - await move_msg.reply(embed=Embed( - title=loc.game.miss_title, - description=loc.game.miss_desc_no_team_guild, - color=Colors.white - )) - await current_cap.send(embed=Embed( - title=loc.game.miss_title, - description=loc.game.miss_desc_no_team_dm.format(move), - color=Colors.white - )) - await other_cap.send(embed=Embed( - title=loc.game.opponents_miss_title, - description=loc.game.opponents_miss_desc.format(move), - color=Colors.white - )) - - if word_count > 0: # If quitting after this move, field will be sent twice in a row + await move_msg.reply( + embed=Embed( + title=loc.game.miss_title, + description=loc.game.miss_desc_no_team_guild, + color=Colors.white, + ) + ) + await current_cap.send( + embed=Embed( + title=loc.game.miss_title, + description=loc.game.miss_desc_no_team_dm.format(move), + color=Colors.white, + ) + ) + await other_cap.send( + embed=Embed( + title=loc.game.opponents_miss_title, + description=loc.game.opponents_miss_desc.format(move), + color=Colors.white, + ) + ) + + if ( + word_count > 0 + ): # If quitting after this move, field will be sent twice in a row await send_fields(game_uuid, channel, current_cap, other_cap) elif move in other_words: - await move_msg.reply(embed=Embed( - title=loc.game.miss_title, - description=loc.game.miss_desc_other_team_guild, - color=Colors.red if other_color == loc.game.red else Colors.blue - )) - await current_cap.send(embed=Embed( - title=loc.game.miss_title, - description=loc.game.miss_desc_other_team_dm.format(move), - color=Colors.red if other_color == loc.game.red else Colors.blue - )) - await other_cap.send(embed=Embed( - title=loc.game.lucky_title, - description=loc.game.lucky_desc_your_team.format(move), - color=Colors.red if other_color == loc.game.red else Colors.blue - )) - - if set(other_words) <= set(opened_words): # If all second_words are opened + await move_msg.reply( + embed=Embed( + title=loc.game.miss_title, + description=loc.game.miss_desc_other_team_guild, + color=Colors.red + if other_color == loc.game.red + else Colors.blue, + ) + ) + await current_cap.send( + embed=Embed( + title=loc.game.miss_title, + description=loc.game.miss_desc_other_team_dm.format(move), + color=Colors.red + if other_color == loc.game.red + else Colors.blue, + ) + ) + await other_cap.send( + embed=Embed( + title=loc.game.lucky_title, + description=loc.game.lucky_desc_your_team.format(move), + color=Colors.red + if other_color == loc.game.red + else Colors.blue, + ) + ) + + if set(other_words) <= set( + opened_words + ): # If all second_words are opened await send_fields(game_uuid, channel, current_cap, other_cap) - await channel.send(embed=Embed( - title=loc.game.game_over_title, - description=loc.game.game_over_desc_all.format(other_color), - color=Colors.red if other_color == loc.game.red else Colors.blue - )) - - await current_cap.send(embed=Embed( - title=loc.game.your_team_lost_title, - description=loc.game.your_team_lost_desc, - color=Colors.red if other_color == loc.game.red else Colors.blue - )) - await self.bot.db.increase_stats(current_cap.id, ("games", "games_cap")) - for player in current_pl: - await player.send(embed=Embed( + await channel.send( + embed=Embed( + title=loc.game.game_over_title, + description=loc.game.game_over_desc_all.format( + other_color + ), + color=Colors.red + if other_color == loc.game.red + else Colors.blue, + ) + ) + + await current_cap.send( + embed=Embed( title=loc.game.your_team_lost_title, description=loc.game.your_team_lost_desc, - color=Colors.red if other_color == loc.game.red else Colors.blue - )) + color=Colors.red + if other_color == loc.game.red + else Colors.blue, + ) + ) + await self.bot.db.increase_stats( + current_cap.id, ("games", "games_cap") + ) + for player in current_pl: + await player.send( + embed=Embed( + title=loc.game.your_team_lost_title, + description=loc.game.your_team_lost_desc, + color=Colors.red + if other_color == loc.game.red + else Colors.blue, + ) + ) await self.bot.db.increase_stats(player.id, ("games",)) - await other_cap.send(embed=Embed( - title=loc.game.your_team_won_title, - description=loc.game.your_team_won_desc, - color=Colors.red if other_color == loc.game.red else Colors.blue - )) - await self.bot.db.increase_stats(other_cap.id, ("games", "games_cap", "wins", "wins_cap")) - for player in other_pl: - await player.send(embed=Embed( + await other_cap.send( + embed=Embed( title=loc.game.your_team_won_title, description=loc.game.your_team_won_desc, - color=Colors.red if other_color == loc.game.red else Colors.blue - )) - await self.bot.db.increase_stats(player.id, ("games", "wins")) + color=Colors.red + if other_color == loc.game.red + else Colors.blue, + ) + ) + await self.bot.db.increase_stats( + other_cap.id, ("games", "games_cap", "wins", "wins_cap") + ) + for player in other_pl: + await player.send( + embed=Embed( + title=loc.game.your_team_won_title, + description=loc.game.your_team_won_desc, + color=Colors.red + if other_color == loc.game.red + else Colors.blue, + ) + ) + await self.bot.db.increase_stats( + player.id, ("games", "wins") + ) game_running = False break break elif move == endgame_word: - await move_msg.reply(embed=Embed( - title=loc.game.miss_title, - description=loc.game.miss_desc_endgame_guild, - color=Colors.black - )) - await current_cap.send(embed=Embed( - title=loc.game.miss_title, - description=loc.game.miss_desc_endgame_dm.format(move), - color=Colors.black - )) - await other_cap.send(embed=Embed( - title=loc.game.lucky_title, - description=loc.game.lucky_desc_endgame.format(move), - color=Colors.black - )) + await move_msg.reply( + embed=Embed( + title=loc.game.miss_title, + description=loc.game.miss_desc_endgame_guild, + color=Colors.black, + ) + ) + await current_cap.send( + embed=Embed( + title=loc.game.miss_title, + description=loc.game.miss_desc_endgame_dm.format(move), + color=Colors.black, + ) + ) + await other_cap.send( + embed=Embed( + title=loc.game.lucky_title, + description=loc.game.lucky_desc_endgame.format(move), + color=Colors.black, + ) + ) await send_fields(game_uuid, channel, current_cap, other_cap) - await channel.send(embed=Embed( - title=loc.game.game_over_title, - description=loc.game.game_over_desc_endgame.format(other_color, current_color), - color=Colors.red if other_color == loc.game.red else Colors.blue - )) - - await current_cap.send(embed=Embed( - title=loc.game.your_team_lost_title, - description=loc.game.your_team_lost_desc, - color=Colors.red if other_color == loc.game.red else Colors.blue - )) - await self.bot.db.increase_stats(current_cap.id, ("games", "games_cap")) - for player in current_pl: - await player.send(embed=Embed( + await channel.send( + embed=Embed( + title=loc.game.game_over_title, + description=loc.game.game_over_desc_endgame.format( + other_color, current_color + ), + color=Colors.red + if other_color == loc.game.red + else Colors.blue, + ) + ) + + await current_cap.send( + embed=Embed( title=loc.game.your_team_lost_title, description=loc.game.your_team_lost_desc, - color=Colors.red if other_color == loc.game.red else Colors.blue - )) + color=Colors.red + if other_color == loc.game.red + else Colors.blue, + ) + ) + await self.bot.db.increase_stats( + current_cap.id, ("games", "games_cap") + ) + for player in current_pl: + await player.send( + embed=Embed( + title=loc.game.your_team_lost_title, + description=loc.game.your_team_lost_desc, + color=Colors.red + if other_color == loc.game.red + else Colors.blue, + ) + ) await self.bot.db.increase_stats(player.id, ("games",)) - await other_cap.send(embed=Embed( - title=loc.game.your_team_won_title, - description=loc.game.your_team_won_desc, - color=Colors.red if other_color == loc.game.red else Colors.blue - )) - await self.bot.db.increase_stats(other_cap.id, ("games", "games_cap", "wins", "wins_cap")) - for player in other_pl: - await player.send(embed=Embed( + await other_cap.send( + embed=Embed( title=loc.game.your_team_won_title, description=loc.game.your_team_won_desc, - color=Colors.red if other_color == loc.game.red else Colors.blue - )) + color=Colors.red + if other_color == loc.game.red + else Colors.blue, + ) + ) + await self.bot.db.increase_stats( + other_cap.id, ("games", "games_cap", "wins", "wins_cap") + ) + for player in other_pl: + await player.send( + embed=Embed( + title=loc.game.your_team_won_title, + description=loc.game.your_team_won_desc, + color=Colors.red + if other_color == loc.game.red + else Colors.blue, + ) + ) await self.bot.db.increase_stats(player.id, ("games", "wins")) game_running = False break else: # They guessed - await move_msg.reply(embed=Embed( - title=loc.game.success_title, - description=loc.game.success_desc_guild, - color=Colors.red if current_color == loc.game.red else Colors.blue - )) - await current_cap.send(embed=Embed( - title=loc.game.success_title, - description=loc.game.success_desc_dm.format(move), - color=Colors.red if current_color == loc.game.red else Colors.blue - )) - await other_cap.send(embed=Embed( - title=loc.game.opponents_success_title, - description=loc.game.opponents_success_desc.format(move), - color=Colors.red if current_color == loc.game.red else Colors.blue - )) - - if set(current_words) <= set(opened_words): # If all first_words are opened + await move_msg.reply( + embed=Embed( + title=loc.game.success_title, + description=loc.game.success_desc_guild, + color=Colors.red + if current_color == loc.game.red + else Colors.blue, + ) + ) + await current_cap.send( + embed=Embed( + title=loc.game.success_title, + description=loc.game.success_desc_dm.format(move), + color=Colors.red + if current_color == loc.game.red + else Colors.blue, + ) + ) + await other_cap.send( + embed=Embed( + title=loc.game.opponents_success_title, + description=loc.game.opponents_success_desc.format(move), + color=Colors.red + if current_color == loc.game.red + else Colors.blue, + ) + ) + + if set(current_words) <= set( + opened_words + ): # If all first_words are opened await send_fields(game_uuid, channel, current_cap, other_cap) - await channel.send(embed=Embed( - title=loc.game.game_over_title, - description=loc.game.game_over_desc_all.format(current_color), - color=Colors.red if current_color == loc.game.red else Colors.blue - )) - - await current_cap.send(embed=Embed( - title=loc.game.your_team_won_title, - description=loc.game.your_team_won_desc, - color=Colors.red if current_color == loc.game.red else Colors.blue - )) - await self.bot.db.increase_stats(current_cap.id, ("games", "games_cap", "wins", "wins_cap")) - for player in current_pl: - await player.send(embed=Embed( + await channel.send( + embed=Embed( + title=loc.game.game_over_title, + description=loc.game.game_over_desc_all.format( + current_color + ), + color=Colors.red + if current_color == loc.game.red + else Colors.blue, + ) + ) + + await current_cap.send( + embed=Embed( title=loc.game.your_team_won_title, description=loc.game.your_team_won_desc, - color=Colors.red if current_color == loc.game.red else Colors.blue - )) - await self.bot.db.increase_stats(player.id, ("games", "wins")) - - await other_cap.send(embed=Embed( - title=loc.game.your_team_lost_title, - description=loc.game.your_team_lost_desc, - color=Colors.red if current_color == loc.game.red else Colors.blue - )) - await self.bot.db.increase_stats(other_cap.id, ("games", "games_cap")) - for player in other_pl: - await player.send(embed=Embed( + color=Colors.red + if current_color == loc.game.red + else Colors.blue, + ) + ) + await self.bot.db.increase_stats( + current_cap.id, ("games", "games_cap", "wins", "wins_cap") + ) + for player in current_pl: + await player.send( + embed=Embed( + title=loc.game.your_team_won_title, + description=loc.game.your_team_won_desc, + color=Colors.red + if current_color == loc.game.red + else Colors.blue, + ) + ) + await self.bot.db.increase_stats( + player.id, ("games", "wins") + ) + + await other_cap.send( + embed=Embed( title=loc.game.your_team_lost_title, description=loc.game.your_team_lost_desc, - color=Colors.red if current_color == loc.game.red else Colors.blue - )) + color=Colors.red + if current_color == loc.game.red + else Colors.blue, + ) + ) + await self.bot.db.increase_stats( + other_cap.id, ("games", "games_cap") + ) + for player in other_pl: + await player.send( + embed=Embed( + title=loc.game.your_team_lost_title, + description=loc.game.your_team_lost_desc, + color=Colors.red + if current_color == loc.game.red + else Colors.blue, + ) + ) await self.bot.db.increase_stats(player.id, ("games",)) game_running = False break - if word_count > 0: # If quitting after this move, field will be sent twice in a row + if ( + word_count > 0 + ): # If quitting after this move, field will be sent twice in a row await send_fields(game_uuid, channel, current_cap, other_cap) word_count -= 1 @@ -601,7 +819,11 @@ async def start(self, interaction: Interaction, team1: list[User], team2: list[U current_words, other_words = other_words, current_words # Sending initial captain filed to the guild text channel - await channel.send(file=File(Paths.cap_img_init(game_uuid), filename="initial_captain_field.png")) + await channel.send( + file=File( + Paths.cap_img_init(game_uuid), filename="initial_captain_field.png" + ) + ) os.remove(Paths.pl_img(game_uuid)) os.remove(Paths.cap_img(game_uuid)) diff --git a/handlers/help_cog.py b/handlers/help_cog.py index 91d5956..700f492 100644 --- a/handlers/help_cog.py +++ b/handlers/help_cog.py @@ -2,7 +2,13 @@ from discord import Embed, Interaction from discord.ext.commands import Context, Command, Cog, command as text_command -from discord.app_commands import Choice, choices, command as app_command, describe, locale_str +from discord.app_commands import ( + Choice, + choices, + command as app_command, + describe, + locale_str, +) from bot import CodenamesBot from misc.util import process_param, if_command_has_check, send_error @@ -13,7 +19,9 @@ class HelpCog(Cog, name="help"): def __init__(self, bot: CodenamesBot) -> None: self.bot = bot - async def help_embed(self, ctx: Context | Interaction, command: str | None, prefix: str) -> Embed | None: + async def help_embed( + self, ctx: Context | Interaction, command: str | None, prefix: str + ) -> Embed | None: sl = isinstance(ctx, Interaction) # kinda short for "slash" loc = await self.bot.db.localization(ctx) @@ -25,35 +33,49 @@ async def help_embed(self, ctx: Context | Interaction, command: str | None, pref await send_error(ctx, loc.errors.title, loc.errors.invalid_command) return - names = comm.name if sl else '{' + '|'.join((comm.name,) + comm.aliases) + '}' + names = ( + comm.name if sl else "{" + "|".join((comm.name,) + comm.aliases) + "}" + ) - params = " ".join(filter( - lambda p: p, - map( - process_param, - *( - [*zip(*comm.clean_params.items())] if comm.clean_params else ((), ()) - ), repeat(sl) + params = " ".join( + filter( + lambda p: p, + map( + process_param, + *( + [*zip(*comm.clean_params.items())] + if comm.clean_params + else ((), ()) + ), + repeat(sl), + ), ) - )) + ) - guild = f"**[{loc.commands.help.guild}]**\n" if if_command_has_check(comm, "guild_only") else "" + guild = ( + f"**[{loc.commands.help.guild}]**\n" + if if_command_has_check(comm, "guild_only") + else "" + ) mod, note = ( - f"**[{loc.commands.help.moderator}]**\n", - f"\n\n_**{loc.commands.help.note}:**\n{loc.commands.help.about_moderator}_" - ) if if_command_has_check(comm, "is_moderator") and ctx.guild else ("", "") + ( + f"**[{loc.commands.help.moderator}]**\n", + f"\n\n_**{loc.commands.help.note}:**\n{loc.commands.help.about_moderator}_", + ) + if if_command_has_check(comm, "is_moderator") and ctx.guild + else ("", "") + ) help_embed = Embed( title=loc.cogs[comm.cog_name].singular, description=f"**`{prefix}{names}{' ' if params else ''}{params}`**\n\n" - f"{guild}{mod}{loc.help[comm.name].help}{note}", - color=Colors.purple + f"{guild}{mod}{loc.help[comm.name].help}{note}", + color=Colors.purple, ) else: help_embed = Embed( - title=loc.commands.help.command_list, - color=Colors.purple + title=loc.commands.help.command_list, color=Colors.purple ) for cog_name, cog in self.bot.cogs.items(): @@ -61,22 +83,35 @@ async def help_embed(self, ctx: Context | Interaction, command: str | None, pref continue def mod(command: Command) -> str: - return f"**[{loc.commands.help.moderator_shortened}]** " \ - if if_command_has_check(command, "is_moderator") and ctx.guild else "" - - cog_comms = tuple(map( - lambda comm: f"**`{prefix}{comm.name}`** - {mod(comm)}{loc.help[comm.name].brief}", - filter(lambda comm: ctx.guild or not if_command_has_check(comm, "guild_only"), cog.get_commands()) - )) + return ( + f"**[{loc.commands.help.moderator_shortened}]** " + if if_command_has_check(command, "is_moderator") and ctx.guild + else "" + ) + + cog_comms = tuple( + map( + lambda comm: f"**`{prefix}{comm.name}`** - {mod(comm)}{loc.help[comm.name].brief}", + filter( + lambda comm: ctx.guild + or not if_command_has_check(comm, "guild_only"), + cog.get_commands(), + ), + ) + ) if cog_comms: - help_embed.add_field(name=loc.cogs[cog_name].plural, value="\n".join(cog_comms), inline=False) + help_embed.add_field( + name=loc.cogs[cog_name].plural, + value="\n".join(cog_comms), + inline=False, + ) help_embed.add_field( name=EMPTY, value=f"**{loc.commands.help.hint}**\n" - f"**`{prefix}{'cdn' if sl else ''}help <{loc.commands.help.command}>`**", - inline=False + f"**`{prefix}{'cdn' if sl else ''}help <{loc.commands.help.command}>`**", + inline=False, ) help_embed.set_thumbnail(url=LOGO_LINK) @@ -91,11 +126,15 @@ def mod(command: Command) -> str: Choice(name="stats", value="stats"), # Choice(name="top", value="top"), Choice(name="language", value="language"), - Choice(name="prefix", value="prefix") + Choice(name="prefix", value="prefix"), ] ) - async def slash_help(self, interaction: Interaction, command: Choice[str] | None = None) -> None: - help_embed = await self.help_embed(interaction, command.value if command else None, "/") + async def slash_help( + self, interaction: Interaction, command: Choice[str] | None = None + ) -> None: + help_embed = await self.help_embed( + interaction, command.value if command else None, "/" + ) if help_embed: # noinspection PyUnresolvedReferences @@ -103,7 +142,9 @@ async def slash_help(self, interaction: Interaction, command: Choice[str] | None @text_command(name="help") async def text_help(self, ctx: Context, command: str | None = None) -> None: - prefix = ctx.message.content.split("help")[0] # Getting a prefix used when calling + prefix = ctx.message.content.split("help")[ + 0 + ] # Getting a prefix used when calling prefix = "cdn " if prefix.strip() == self.bot.user.mention else prefix help_embed = await self.help_embed(ctx, command, prefix) diff --git a/handlers/maintenance_cog.py b/handlers/maintenance_cog.py index 1b16353..73ee2f9 100644 --- a/handlers/maintenance_cog.py +++ b/handlers/maintenance_cog.py @@ -35,7 +35,9 @@ async def save(self, ctx: Context) -> None: @command() @is_owner() async def load(self, ctx: Context) -> None: - if len(ctx.message.attachments) != 1 or not ctx.message.attachments[0].filename.endswith(".db"): + if len(ctx.message.attachments) != 1 or not ctx.message.attachments[ + 0 + ].filename.endswith(".db"): await ctx.message.add_reaction("❔") await ctx.message.delete(delay=3) return diff --git a/handlers/settings_cog.py b/handlers/settings_cog.py index c864619..82e204b 100644 --- a/handlers/settings_cog.py +++ b/handlers/settings_cog.py @@ -21,17 +21,26 @@ async def prefix(self, ctx: Context, new_prefix: str = "cdn") -> None: loc = await self.bot.db.localization(ctx) if ctx.guild: - await self.bot.db.exec_and_commit("UPDATE guilds SET prefix = ? WHERE id = ?", (new_prefix, ctx.guild.id)) + await self.bot.db.exec_and_commit( + "UPDATE guilds SET prefix = ? WHERE id = ?", (new_prefix, ctx.guild.id) + ) else: - await self.bot.db.exec_and_commit("UPDATE players SET prefix = ? WHERE id = ?", (new_prefix, ctx.author.id)) - - await ctx.send(embed=Embed( - title=loc.commands.prefix.prefix_changed_title, - description=loc.commands.prefix.prefix_changed_desc.format( - loc.commands.prefix.new_prefix.format(new_prefix) if new_prefix else loc.commands.prefix.prefix_deleted - ), - color=Colors.purple - )) + await self.bot.db.exec_and_commit( + "UPDATE players SET prefix = ? WHERE id = ?", + (new_prefix, ctx.author.id), + ) + + await ctx.send( + embed=Embed( + title=loc.commands.prefix.prefix_changed_title, + description=loc.commands.prefix.prefix_changed_desc.format( + loc.commands.prefix.new_prefix.format(new_prefix) + if new_prefix + else loc.commands.prefix.prefix_deleted + ), + color=Colors.purple, + ) + ) @hybrid_command(aliases=("lang",), description=locale_str("language")) @is_moderator() @@ -40,8 +49,10 @@ async def language(self, ctx: Context) -> None: language_embed = Embed( title=loc.commands.language.title, - description=loc.commands.language.desc_current.format(loc.literal.upper(), flags_loc[loc.literal]), - color=Colors.purple + description=loc.commands.language.desc_current.format( + loc.literal.upper(), flags_loc[loc.literal] + ), + color=Colors.purple, ) await ctx.send(embed=language_embed, view=LocalizationView()) diff --git a/handlers/translator.py b/handlers/translator.py index 17ed5c5..05f69c3 100644 --- a/handlers/translator.py +++ b/handlers/translator.py @@ -1,13 +1,23 @@ from discord import Locale -from discord.app_commands import Translator, locale_str, TranslationContextTypes, TranslationContextLocation +from discord.app_commands import ( + Translator, + locale_str, + TranslationContextTypes, + TranslationContextLocation, +) from misc.messages import messages -class CodenamesTranslator(Translator): # only used for command and parameter descriptions - async def translate(self, string: locale_str, locale: Locale, context: TranslationContextTypes) -> str | None: +class CodenamesTranslator( + Translator +): # only used for command and parameter descriptions + async def translate( + self, string: locale_str, locale: Locale, context: TranslationContextTypes + ) -> str | None: if context.location not in ( - TranslationContextLocation.command_description, TranslationContextLocation.parameter_description + TranslationContextLocation.command_description, + TranslationContextLocation.parameter_description, ): return diff --git a/handlers/ui.py b/handlers/ui.py index 6a5c524..794ad54 100644 --- a/handlers/ui.py +++ b/handlers/ui.py @@ -24,12 +24,12 @@ async def callback(self, interaction: Interaction) -> None: if interaction.guild: await db.exec_and_commit( "UPDATE guilds SET localization = ? WHERE id = ?", - (new_loc, interaction.guild.id) + (new_loc, interaction.guild.id), ) else: await db.exec_and_commit( "UPDATE players SET localization = ? WHERE id = ?", - (new_loc, interaction.user.id) + (new_loc, interaction.user.id), ) loc = await db.localization(interaction) @@ -38,10 +38,12 @@ async def callback(self, interaction: Interaction) -> None: await interaction.response.edit_message( embed=Embed( title=loc.commands.language.title, - description=loc.commands.language.desc_set.format(new_loc.upper(), self.flag), - color=Colors.purple + description=loc.commands.language.desc_set.format( + new_loc.upper(), self.flag + ), + color=Colors.purple, ), - view=None + view=None, ) @@ -60,7 +62,7 @@ def __init__( self, loc: Localization, start_callback: Callable[[Interaction, list[User], list[User]], Coroutine], - caller_id: int + caller_id: int, ) -> None: super().__init__() @@ -88,45 +90,48 @@ def remove_player(self, player: User) -> None: elif player in self.team2: self.team2.remove(player) - async def update_player_list(self, interaction: Interaction, *, final: bool = False) -> None: + async def update_player_list( + self, interaction: Interaction, *, final: bool = False + ) -> None: if self.no_team or self.team1 or self.team2: players_embed = Embed( - title=self.loc.commands.game.registration_over if final else self.loc.commands.game.registration, - color=Colors.purple + title=self.loc.commands.game.registration_over + if final + else self.loc.commands.game.registration, + color=Colors.purple, ) players_embed.add_field( name=self.loc.commands.game.team1, - value="\n".join(map(lambda p: p.mention, self.team1)) or EMPTY + value="\n".join(map(lambda p: p.mention, self.team1)) or EMPTY, ) if not final: players_embed.add_field( name=self.loc.commands.game.no_team, - value="\n".join(map(lambda p: p.mention, self.no_team)) or EMPTY + value="\n".join(map(lambda p: p.mention, self.no_team)) or EMPTY, ) players_embed.add_field( name=self.loc.commands.game.team2, - value="\n".join(map(lambda p: p.mention, self.team2)) or EMPTY + value="\n".join(map(lambda p: p.mention, self.team2)) or EMPTY, ) else: players_embed = Embed( title=self.loc.commands.game.registration, description=self.loc.commands.game.empty_list, - color=Colors.purple + color=Colors.purple, ) if not final: players_embed.add_field( name=EMPTY, value=f"_{self.loc.commands.game.registration_instructions}_", - inline=False + inline=False, ) await interaction.followup.edit_message( - interaction.message.id, - embed=players_embed, view=None if final else self + interaction.message.id, embed=players_embed, view=None if final else self ) @button(emoji="1️⃣", style=ButtonStyle.grey, row=1) @@ -169,28 +174,50 @@ async def leave_button(self, interaction: Interaction, button: Button) -> None: @button(emoji="⛔", style=ButtonStyle.red, row=2) async def cancel_button(self, interaction: Interaction, button: Button) -> None: - if not interaction.user.id == self.caller_id and not await is_moderator().predicate(interaction): + if ( + not interaction.user.id == self.caller_id + and not await is_moderator().predicate(interaction) + ): # noinspection PyUnresolvedReferences - await interaction.response.defer() # so ctx.followup.send() from send_error won't crash + await ( + interaction.response.defer() + ) # so ctx.followup.send() from send_error won't crash await send_error( - interaction, self.loc.errors.title, self.loc.errors.no_permission.format(self.loc.errors.not_a_mod) + interaction, + self.loc.errors.title, + self.loc.errors.no_permission.format(self.loc.errors.not_a_mod), ) return - await send_alert(interaction, self.loc, self.loc.ui.cancel_reg, self.cancel_callback, interaction) + await send_alert( + interaction, + self.loc, + self.loc.ui.cancel_reg, + self.cancel_callback, + interaction, + ) @button(emoji="▶️", style=ButtonStyle.green, row=2) async def start_button(self, interaction: Interaction, button: Button) -> None: if interaction.user not in self.no_team + self.team1 + self.team2: # noinspection PyUnresolvedReferences - await interaction.response.defer() # so ctx.followup.send() from send_error won't crash + await ( + interaction.response.defer() + ) # so ctx.followup.send() from send_error won't crash await send_error( - interaction, self.loc.errors.title, self.loc.errors.no_permission.format(self.loc.errors.not_registered) + interaction, + self.loc.errors.title, + self.loc.errors.no_permission.format(self.loc.errors.not_registered), ) return - await send_alert(interaction, self.loc, self.loc.ui.start_game, self.start_pre_callback, interaction) - + await send_alert( + interaction, + self.loc, + self.loc.ui.start_game, + self.start_pre_callback, + interaction, + ) async def start_pre_callback(self, interaction: Interaction) -> None: if self.game_started: # ignoring duplicate starts from same registration @@ -208,11 +235,15 @@ async def start_pre_callback(self, interaction: Interaction) -> None: team2_temp.append(member) if len(team1_temp) < 2 or len(team2_temp) < 2: - await send_error(interaction, self.loc.errors.title, self.loc.errors.not_enough_players) + await send_error( + interaction, self.loc.errors.title, self.loc.errors.not_enough_players + ) return if len(team1_temp) > 25 or len(team2_temp) > 25: - await send_error(interaction, self.loc.errors.title, self.loc.errors.too_many_players) + await send_error( + interaction, self.loc.errors.title, self.loc.errors.too_many_players + ) return self.no_team = [] @@ -222,18 +253,20 @@ async def start_pre_callback(self, interaction: Interaction) -> None: # Adding players to the database db = Database() all_players_ids = tuple(map(lambda p: p.id, self.team1 + self.team2)) - registered_ids = tuple(map( - lambda row: row[0], - await db.fetch( - f"SELECT id FROM players WHERE id IN ({', '.join(map(str, all_players_ids))})", - fetchall=True + registered_ids = tuple( + map( + lambda row: row[0], + await db.fetch( + f"SELECT id FROM players WHERE id IN ({', '.join(map(str, all_players_ids))})", + fetchall=True, + ), ) - )) + ) for id in all_players_ids: if id not in registered_ids: await db.exec_and_commit( "INSERT INTO players VALUES (?, strftime('%d/%m/%Y','now'), ?, ?, ?, ?, ?, ?)", - (id, "", self.loc.literal, 0, 0, 0, 0) + (id, "", self.loc.literal, 0, 0, 0, 0), ) self.game_started = True @@ -245,11 +278,9 @@ async def start_pre_callback(self, interaction: Interaction) -> None: # noinspection PyMethodMayBeStatic async def cancel_callback(self, interaction: Interaction) -> None: registration_cancelled_embed = Embed( - title=self.loc.commands.game.registration_cancelled, - color=Colors.purple + title=self.loc.commands.game.registration_cancelled, color=Colors.purple ) await interaction.followup.edit_message( - interaction.message.id, - embed=registration_cancelled_embed, view=None + interaction.message.id, embed=registration_cancelled_embed, view=None ) diff --git a/misc/constants.py b/misc/constants.py index 46aa7fe..9f89ffb 100644 --- a/misc/constants.py +++ b/misc/constants.py @@ -16,43 +16,41 @@ REACTION_NUMBERS = ("1️⃣", "2️⃣", "3️⃣", "4️⃣", "5️⃣", "6️⃣", "7️⃣", "8️⃣", "9️⃣") -font = ImageFont.truetype(str(Path("resources", "fonts", "RobotoCondensed-Bold.ttf")), 80, encoding="utf-8") -big_font = ImageFont.truetype(str(Path("resources", "fonts", "Roboto-Bold.ttf")), 350, encoding="utf-8") +font = ImageFont.truetype( + str(Path("resources", "fonts", "RobotoCondensed-Bold.ttf")), 80, encoding="utf-8" +) +big_font = ImageFont.truetype( + str(Path("resources", "fonts", "Roboto-Bold.ttf")), 350, encoding="utf-8" +) dictionaries = { "en": { - "std": "**Original** English dictionary (400 words)", - "duet": "**Original Duet** dictionary (400 words)", - "deep": "**Original Deep Undercover** dictionary (**18+**, 390 words)", - "denull": "**deNULL's** dictionary (763 words)", + "std": "**Original** English dictionary (400 words)", + "duet": "**Original Duet** dictionary (400 words)", + "deep": "**Original Deep Undercover** dictionary (**18+**, 390 words)", + "denull": "**deNULL's** dictionary (763 words)", "denull18": "**deNULL's** dictionary (**18+**, 1081 words)", - "all": "**All** English dictionaries (**18+**, 1139 words)", - "esp": "**Esperanto**" + "all": "**All** English dictionaries (**18+**, 1139 words)", + "esp": "**Esperanto**", }, "ru": { - "std": "**Стандартный** словарь из локализации GaGa Games (400 слов)", - "deep": "Словарь версии **Deep Undercover**, GaGa Games (**18+**, 390 слов)", - "pard": "Словарь от **Pard** (302 слова)", - "vpupkin": "Словарь от **vpupkin** (396 слов, много топонимов)", - "zav": "Словарь от **Ивана Заворина** (2272 частых слов)", - "denull": "Словарь от **deNULL** (636 слов, немного топонимов)", + "std": "**Стандартный** словарь из локализации GaGa Games (400 слов)", + "deep": "Словарь версии **Deep Undercover**, GaGa Games (**18+**, 390 слов)", + "pard": "Словарь от **Pard** (302 слова)", + "vpupkin": "Словарь от **vpupkin** (396 слов, много топонимов)", + "zav": "Словарь от **Ивана Заворина** (2272 частых слов)", + "denull": "Словарь от **deNULL** (636 слов, немного топонимов)", "denull18": "Словарь от **deNULL** (**18+**, 1014 слов)", - "all": "**Все** словари **вместе** (**18+**, 1058 слов)", - "esp": "**Esperanto**" - } + "all": "**Все** словари **вместе** (**18+**, 1058 слов)", + "esp": "**Esperanto**", + }, } -flags_loc = { - "en": "🇬🇧", - "ru": "🇷🇺" -} +flags_loc = {"en": "🇬🇧", "ru": "🇷🇺"} flags_loc_rev = {v: k for k, v in flags_loc.items()} -flags_lang = { - "en": "🇬🇧", - "ru": "🇷🇺" -} +flags_lang = {"en": "🇬🇧", "ru": "🇷🇺"} flags_lang_rev = {v: k for k, v in flags_lang.items()} @@ -88,7 +86,9 @@ class FieldSizing: card_count = 5 card_spacing = 50 card_width = (width - (card_spacing * (card_count + 1))) / card_count - card_height = (height - footer_height - (card_spacing * (card_count + 1))) / card_count + card_height = ( + height - footer_height - (card_spacing * (card_count + 1)) + ) / card_count card_radius = 10 card_outline_width = 2 diff --git a/misc/database.py b/misc/database.py index 6232262..94c12bd 100644 --- a/misc/database.py +++ b/misc/database.py @@ -57,13 +57,12 @@ async def close(cls) -> None: await cls._db.close() - async def fetch( self, sql: str, parameters: Iterable[Any] | None = None, *, - fetchall: bool = False + fetchall: bool = False, ) -> tuple[Any] | None: """ Executes the given SQL query and returns the result. @@ -80,7 +79,9 @@ async def fetch( return tuple(res) if res else (tuple() if fetchall else None) - async def exec_and_commit(self, sql: str, parameters: Iterable[Any] | None = None) -> None: + async def exec_and_commit( + self, sql: str, parameters: Iterable[Any] | None = None + ) -> None: """ Executes the given SQL statement with changes to the database and commits them. @@ -92,7 +93,6 @@ async def exec_and_commit(self, sql: str, parameters: Iterable[Any] | None = Non await self._db.execute(sql, parameters) await self._db.commit() - async def localization(self, ctx: Context | Interaction) -> Localization: """ Determines which language the bot should use. @@ -102,18 +102,26 @@ async def localization(self, ctx: Context | Interaction) -> Localization: """ if ctx.guild: - request = await self.fetch("SELECT localization FROM guilds WHERE id = ?", (ctx.guild.id,)) + request = await self.fetch( + "SELECT localization FROM guilds WHERE id = ?", (ctx.guild.id,) + ) if not request: # should not normally happen - await self.exec_and_commit("INSERT INTO guilds VALUES (?, ?, ?)", (ctx.guild.id, "", "en")) + await self.exec_and_commit( + "INSERT INTO guilds VALUES (?, ?, ?)", (ctx.guild.id, "", "en") + ) else: user_id = ctx.author.id if isinstance(ctx, Context) else ctx.user.id - request = await self.fetch("SELECT localization FROM players WHERE id = ?", (user_id,)) + request = await self.fetch( + "SELECT localization FROM players WHERE id = ?", (user_id,) + ) - if not request: # if the user sends a slash command to the bot as the first use in DMs + if ( + not request + ): # if the user sends a slash command to the bot as the first use in DMs await self.exec_and_commit( "INSERT INTO players VALUES (?, strftime('%d/%m/%Y','now'), ?, ?, ?, ?, ?, ?)", - (user_id, "", "en", 0, 0, 0, 0) + (user_id, "", "en", 0, 0, 0, 0), ) return messages[request[0]] if request else messages["en"] @@ -128,14 +136,16 @@ async def increase_stats(self, player_id: int, stats: Iterable[str]) -> None: """ if not ( - current_stats := await self.fetch(f"SELECT {', '.join(stats)} FROM players WHERE id = ?", (player_id,)) + current_stats := await self.fetch( + f"SELECT {', '.join(stats)} FROM players WHERE id = ?", (player_id,) + ) ): # should not normally happen await self.exec_and_commit( "INSERT INTO players VALUES (?, strftime('%d/%m/%Y','now'), ?, ?, ?, ?, ?, ?)", - (player_id, "", "en", 0, 0, 0, 0) + (player_id, "", "en", 0, 0, 0, 0), ) await self.exec_and_commit( f"UPDATE players SET {' = ?, '.join(stats)} = ? WHERE id = ?", - (*map(lambda stat: stat + 1, current_stats), player_id) + (*map(lambda stat: stat + 1, current_stats), player_id), ) diff --git a/misc/generation.py b/misc/generation.py index 067e5d8..591e839 100644 --- a/misc/generation.py +++ b/misc/generation.py @@ -1,6 +1,5 @@ -import math import random -from typing import Iterable, Sequence +from typing import Iterable, Sequence, cast import aiofiles from PIL import Image, ImageDraw @@ -8,11 +7,19 @@ from misc.constants import font, big_font, Paths, FieldSizing, Colors +FIRST_TEAM_WORD_COUNT = 9 +SECOND_TEAM_WORD_COUNT = 8 + + # noinspection PyUnboundLocalVariable def field( - team1_words: Iterable[str], team2_words: Iterable[str], endgame_word: str, no_team_words: Iterable[str], - opened_words: Iterable[str], order: Sequence[str], - game_uuid: str + team1_words: Iterable[str], + team2_words: Iterable[str], + endgame_word: str, + no_team_words: Iterable[str], + opened_words: Iterable[str], + order: Sequence[str], + game_uuid: str, ) -> None: """ Creates and saves captain and player fields as images from the given game word lists. @@ -36,7 +43,7 @@ def field( 0, FieldSizing.height - FieldSizing.footer_height, FieldSizing.width / 2 - 1, - FieldSizing.height - 1 + FieldSizing.height - 1, ), fill=Colors.red_fill, ) @@ -45,14 +52,11 @@ def field( if word not in opened_words: red_words_left += 1 draw.text( - xy=( - FieldSizing.width / 4, - FieldSizing.height - FieldSizing.footer_height / 2 - ), + xy=(FieldSizing.width / 4, FieldSizing.height - FieldSizing.footer_height / 2), text=str(red_words_left), fill=Colors.red_font, font=big_font, - anchor=FieldSizing.text_anchor + anchor=FieldSizing.text_anchor, ) draw.rectangle( @@ -60,7 +64,7 @@ def field( FieldSizing.width / 2, FieldSizing.height - FieldSizing.footer_height, FieldSizing.width - 1, - FieldSizing.height - 1 + FieldSizing.height - 1, ), fill=Colors.blue_fill, ) @@ -71,12 +75,12 @@ def field( draw.text( xy=( FieldSizing.width / 2 + FieldSizing.width / 4, - FieldSizing.height - FieldSizing.footer_height / 2 + FieldSizing.height - FieldSizing.footer_height / 2, ), text=str(blue_words_left), fill=Colors.blue_font, font=big_font, - anchor=FieldSizing.text_anchor + anchor=FieldSizing.text_anchor, ) # Creating two separate images for two fields @@ -131,23 +135,27 @@ def field( FieldSizing.card_spacing * (x + 1) + FieldSizing.card_width * x, FieldSizing.card_spacing * (y + 1) + FieldSizing.card_height * y, (FieldSizing.card_spacing + FieldSizing.card_width) * (x + 1), - (FieldSizing.card_spacing + FieldSizing.card_height) * (y + 1) + (FieldSizing.card_spacing + FieldSizing.card_height) * (y + 1), ), radius=FieldSizing.card_radius, fill=fill_col, outline=outline_col, - width=FieldSizing.card_outline_width + width=FieldSizing.card_outline_width, ) cap_draw.text( xy=( - FieldSizing.card_spacing * (x + 1) + FieldSizing.card_width * x + FieldSizing.card_width / 2, - FieldSizing.card_spacing * (y + 1) + FieldSizing.card_height * y + FieldSizing.card_height / 2 + FieldSizing.card_spacing * (x + 1) + + FieldSizing.card_width * x + + FieldSizing.card_width / 2, + FieldSizing.card_spacing * (y + 1) + + FieldSizing.card_height * y + + FieldSizing.card_height / 2, ), text=str(word).upper(), fill=font_col, font=font, - anchor=FieldSizing.text_anchor + anchor=FieldSizing.text_anchor, ) # Filling the players' field @@ -181,30 +189,36 @@ def field( FieldSizing.card_spacing * (x + 1) + FieldSizing.card_width * x, FieldSizing.card_spacing * (y + 1) + FieldSizing.card_height * y, (FieldSizing.card_spacing + FieldSizing.card_width) * (x + 1), - (FieldSizing.card_spacing + FieldSizing.card_height) * (y + 1) + (FieldSizing.card_spacing + FieldSizing.card_height) * (y + 1), ), radius=FieldSizing.card_radius, fill=fill_col, outline=outline_col, - width=FieldSizing.card_outline_width + width=FieldSizing.card_outline_width, ) pl_draw.text( xy=( - FieldSizing.card_spacing * (x + 1) + FieldSizing.card_width * x + FieldSizing.card_width / 2, - FieldSizing.card_spacing * (y + 1) + FieldSizing.card_height * y + FieldSizing.card_height / 2 + FieldSizing.card_spacing * (x + 1) + + FieldSizing.card_width * x + + FieldSizing.card_width / 2, + FieldSizing.card_spacing * (y + 1) + + FieldSizing.card_height * y + + FieldSizing.card_height / 2, ), text=str(word).upper(), fill=font_col, font=font, - anchor=FieldSizing.text_anchor + anchor=FieldSizing.text_anchor, ) cap_img.save(Paths.cap_img(game_uuid)) pl_img.save(Paths.pl_img(game_uuid)) -async def words(lang: str, dict_name: str) -> tuple[tuple[str], tuple[str], str, tuple[str]]: +async def words( + lang: str, dict_name: str +) -> tuple[tuple[str], tuple[str], str, tuple[str, ...]]: """ Returns game words randomly picked from the given dictionary @@ -213,23 +227,32 @@ async def words(lang: str, dict_name: str) -> tuple[tuple[str], tuple[str], str, :return: Game words: red (tuple), blue (tuple), endgame (str), no team (tuple) """ - async with aiofiles.open(Paths.dictionary(lang, dict_name), "r", encoding="utf-8") as dictionary: + async with aiofiles.open( + Paths.dictionary(lang, dict_name), "r", encoding="utf-8" + ) as dictionary: all_words = (await dictionary.read()).lower().replace("ё", "е").split("\n") words: set[str] = set(random.sample(all_words, 25)) - endgame_word = random.sample(tuple(words), 1)[0] + endgame_word: str = random.choice(tuple(words)) words.remove(endgame_word) - first_team = math.floor(random.random() * 2) - if first_team: - team1_words = random.sample(tuple(words), 9) + if bool(random.randint(0, 1)): + team1_words: tuple[str] = tuple( + random.sample(tuple(words), FIRST_TEAM_WORD_COUNT) + ) words.difference_update(team1_words) - team2_words = random.sample(tuple(words), 8) - no_team_words = words.difference(team2_words) + team2_words: tuple[str] = tuple( + random.sample(tuple(words), SECOND_TEAM_WORD_COUNT) + ) + no_team_words: tuple[str] = cast( + tuple[str], tuple(words.difference(team2_words)) + ) else: - team2_words = random.sample(tuple(words), 9) + team2_words: tuple[str] = random.sample(tuple(words), FIRST_TEAM_WORD_COUNT) words.difference_update(team2_words) - team1_words = random.sample(tuple(words), 8) - no_team_words = words.difference(team1_words) + team1_words: tuple[str] = random.sample(tuple(words), SECOND_TEAM_WORD_COUNT) + no_team_words: tuple[str] = cast( + tuple[str], tuple(words.difference(team1_words)) + ) - return tuple(team1_words), tuple(team2_words), endgame_word, tuple(no_team_words) + return team1_words, team2_words, endgame_word, no_team_words diff --git a/misc/messages.py b/misc/messages.py index 39bdf7b..95b08ad 100644 --- a/misc/messages.py +++ b/misc/messages.py @@ -69,6 +69,7 @@ class HelpCommand: hint: str command: str + @dataclass # noqa: E302 class GameCommand: registration: str @@ -81,6 +82,7 @@ class GameCommand: no_team: str empty_list: str + @dataclass # noqa: E302 class StartCommand: lang_selection_title: str @@ -92,6 +94,7 @@ class StartCommand: cap_selected_title: str cap_selected_desc: str + @dataclass # noqa: E302 class StatsCommand: smbs_stats: str @@ -105,6 +108,7 @@ class StatsCommand: note: str egg_game_master_desc: str + @dataclass # noqa: E302 class PrefixCommand: prefix_changed_title: str @@ -112,6 +116,7 @@ class PrefixCommand: new_prefix: str prefix_deleted: str + @dataclass # noqa: E302 class LanguageCommand: title: str @@ -119,6 +124,7 @@ class LanguageCommand: desc_set: str desc_aborted: str + @dataclass # noqa: E302 class Commands: help: HelpCommand @@ -191,12 +197,12 @@ class Localization: start_notification_title="Game started", start_notification_desc_cap="**You are the captain of the {} team**\n\nYour teammates are:\n{}", start_notification_desc_pl="**You're a member of the {} team**\n\nThe captain of your team is {}\n\n" - "Your teammates are:\n{}", + "Your teammates are:\n{}", waiting_title="Waiting for move", waiting_desc_cap="Captain of **{}** team", waiting_desc_pl="Players of **{}** team", pl_move_instructions="Type words you want to open in response messages.\n" - "To **FINISH THE MOVE** type **`0`**\nTo **STOP THE GAME** type **`000`**", + "To **FINISH THE MOVE** type **`0`**\nTo **STOP THE GAME** type **`000`**", cap_move_request_title="Your turn", cap_move_request_desc="Type a word and a number in response message, for example: **`{}`**", cap_move_accepted="Move accepted", @@ -231,7 +237,7 @@ class Localization: game_stopped_title="GAME STOPPED", game_stopped_desc="Most players voted for game stopping", game_continued_title="GAME CONTINUES", - game_continued_desc="Most players voted against game stopping" + game_continued_desc="Most players voted against game stopping", ), help={ "help": HelpAndBrief( @@ -240,12 +246,8 @@ class Localization: "help_command_param": HelpAndBrief( help="The command to show detailed help for" ), - "game": HelpAndBrief( - help="Start registration for a new game" - ), - "stats": HelpAndBrief( - help="Show player's statistics" - ), + "game": HelpAndBrief(help="Start registration for a new game"), + "stats": HelpAndBrief(help="Show player's statistics"), "stats_member_param": HelpAndBrief( help="Server member whose statistics will be displayed" ), @@ -254,24 +256,16 @@ class Localization: ), "prefix": HelpAndBrief( help="Change the text command prefix\nTo set it to default (**`cdn`**) do not provide any.", - brief="Change the text command prefix, empty prefix - default" - ), - "prefix_new_prefix_param": HelpAndBrief( - help="New text command prefix" + brief="Change the text command prefix, empty prefix - default", ), + "prefix_new_prefix_param": HelpAndBrief(help="New text command prefix"), "language": HelpAndBrief( help="Change the language of the bot's messages (**EN**/**RU**)" - ) + ), }, cogs={ - "game": CogName( - singular="Game command", - plural="Game commands" - ), - "settings": CogName( - singular="Setting command", - plural="Setting commands" - ) + "game": CogName(singular="Game command", plural="Game commands"), + "settings": CogName(singular="Setting command", plural="Setting commands"), }, commands=Commands( help=HelpCommand( @@ -280,10 +274,10 @@ class Localization: moderator="Moderator", moderator_shortened="Mod", about_moderator="Moderator is the member who can manage messages in the channel where the command was " - "called", + "called", note="Note", hint="Learn a more detailed description of the command:", - command="command" + command="command", ), game=GameCommand( registration="Registration", @@ -294,7 +288,7 @@ class Localization: team1="Team 1", team2="Team 2", no_team="No team", - empty_list="Nobody is ready to play :(" + empty_list="Nobody is ready to play :(", ), start=StartCommand( lang_selection_title="Select game language", @@ -304,7 +298,7 @@ class Localization: cap_selection_title="**{}** team: Voting for the captain", cap_selection_desc="**R** - Random captain\n\n{}\n\nYou have 15 seconds to vote", cap_selected_title="**{}** team: Captain selected", - cap_selected_desc="Your captain is {}" + cap_selected_desc="Your captain is {}", ), stats=StatsCommand( smbs_stats="{}'s statistics", @@ -316,21 +310,21 @@ class Localization: games_won="Games won", winrate="Winrate", note="Codenames is a **team game**, so the winrate statistics **do not** exactly reflect player's " - "skill", - egg_game_master_desc="Best game master: **100%**" + "skill", + egg_game_master_desc="Best game master: **100%**", ), prefix=PrefixCommand( prefix_changed_title="Prefix changed", prefix_changed_desc="{}\nSlash-commands, default one **`cdn`** and bot ping are still valid", new_prefix="New prefix:\n**`{}`**\n", - prefix_deleted="Custom prefix deleted" + prefix_deleted="Custom prefix deleted", ), language=LanguageCommand( title="Language settings", desc_current="**Current language: {} {}**\n\n_Select new language:_\n\n", desc_set="**Language is set to {} {}**", - desc_aborted="Aborted" - ) + desc_aborted="Aborted", + ), ), ui=UI( alert_title="Alert", @@ -340,7 +334,7 @@ class Localization: random="Random team", leave="Leave", start_game="Start the game", - cancel_reg="Cancel the registration" + cancel_reg="Cancel the registration", ), errors=Errors( title="Error", @@ -352,8 +346,8 @@ class Localization: invalid_command="Invalid command", not_enough_players="**Not enough players**\n**Each** team must have **at least 2** players.", too_many_players="**Too much players**\n**Each** team must have **no more than 25** players.", - never_played="{} haven't played Codenames yet" - ) + never_played="{} haven't played Codenames yet", + ), ), "ru": Localization( literal="ru", @@ -364,12 +358,12 @@ class Localization: start_notification_title="Игра началась", start_notification_desc_cap="**Вы - капитан команды {}**\n\nС Вами в команде:\n{}", start_notification_desc_pl="**Вы - участник команды {}**\n\nКапитан Вашей команды - {}\n\nС Вами в " - "команде:\n{}", + "команде:\n{}", waiting_title="Ожидание хода", waiting_desc_cap="Капитан команды **{}**", waiting_desc_pl="Игроки команды **{}**", pl_move_instructions="В ответных сообщениях напишите слова, которые вы хотите открыть.\nЧтобы " - "**ЗАКОНЧИТЬ ХОД**, напишите **`0`**\nЧтобы **ОСТАНОВИТЬ ИГРУ**, напишите **`000`**", + "**ЗАКОНЧИТЬ ХОД**, напишите **`0`**\nЧтобы **ОСТАНОВИТЬ ИГРУ**, напишите **`000`**", cap_move_request_title="Ваш ход", cap_move_request_desc="В ответном сообщении напишите слово и число, например: **`{}`**", cap_move_accepted="Ход принят", @@ -389,7 +383,7 @@ class Localization: miss_desc_endgame_dm="Ваша команда открыла слово **`{}`**, которое **положит конец игре**", opponents_miss_title="Промах оппонента", opponents_miss_desc="Команда противника открыла слово **`{}`**, " - "которое **не принадлежит ни одной команде**", + "которое **не принадлежит ни одной команде**", lucky_title="Удача!", lucky_desc_your_team="Команда противника открыла слово **`{}`**, которое **принадлежит вашей команде**", lucky_desc_endgame="Команда противника открыла слово **`{}`**, которое **положит конец игре**", @@ -402,11 +396,11 @@ class Localization: your_team_lost_desc="Удачи в следующей игре!", voting_for_stopping_title="Остановка игры", voting_for_stopping_desc="**Вы уверены, что хотите прекратить игру?**\n\n" - "Всем игрокам на голосование дается 15 секунд", + "Всем игрокам на голосование дается 15 секунд", game_stopped_title="ИГРА ПРЕКРАЩЕНА", game_stopped_desc="Большинство игроков проголосовало за остановку игры", game_continued_title="ИГРА ПРОДОЛЖАЕТСЯ", - game_continued_desc="Большинство игроков проголосовало против остановки игры" + game_continued_desc="Большинство игроков проголосовало против остановки игры", ), help={ "help": HelpAndBrief( @@ -415,39 +409,27 @@ class Localization: "help_command_param": HelpAndBrief( help="Команда, для которой будет выведено подробное описание" ), - "game": HelpAndBrief( - help="Запустить регистрацию на новую игру" - ), - "stats": HelpAndBrief( - help="Показать статистику игрока" - ), + "game": HelpAndBrief(help="Запустить регистрацию на новую игру"), + "stats": HelpAndBrief(help="Показать статистику игрока"), "stats_member_param": HelpAndBrief( help="Участник сервера, чья статистика будет выведена" ), - "stats_show_param": HelpAndBrief( - help="Будет ли сообщение видно всем" - ), + "stats_show_param": HelpAndBrief(help="Будет ли сообщение видно всем"), "prefix": HelpAndBrief( help="Изменить префикс для текстовых команд\nЧтобы сбросить до префикса по умолчанию (**`cdn`**), " - "новый указывать не нужно.", - brief="Изменить префикс для текстовых команд, пустой префикс - по умолчанию" + "новый указывать не нужно.", + brief="Изменить префикс для текстовых команд, пустой префикс - по умолчанию", ), "prefix_new_prefix_param": HelpAndBrief( help="Новый префикс для текстовых команд" ), "language": HelpAndBrief( help="Изменить язык сообщений бота (**РУС**/**АНГ**)" - ) + ), }, cogs={ - "game": CogName( - singular="Команда для игры", - plural="Команды для игры" - ), - "settings": CogName( - singular="Команда настроек", - plural="Команды настроек" - ) + "game": CogName(singular="Команда для игры", plural="Команды для игры"), + "settings": CogName(singular="Команда настроек", plural="Команды настроек"), }, commands=Commands( help=HelpCommand( @@ -456,10 +438,10 @@ class Localization: moderator="Модератор", moderator_shortened="Модер", about_moderator="Модератор - это пользователь, который имеет право управления сообщениями в канале, " - "где была вызвана команда", + "где была вызвана команда", note="Заметка", hint="Получить более подробное описание команды:", - command="команда" + command="команда", ), game=GameCommand( registration="Регистрация", @@ -470,7 +452,7 @@ class Localization: team1="Команда 1", team2="Команда 2", no_team="Случайная команда", - empty_list="Никто не готов играть :(" + empty_list="Никто не готов играть :(", ), start=StartCommand( lang_selection_title="Выберите язык игры", @@ -480,7 +462,7 @@ class Localization: cap_selection_title="Команда **{}**: Голосование за капитана", cap_selection_desc="**R** - Случайный капитан\n\n{}\n\nНа голосование дается 15 секунд", cap_selected_title="Команда **{}**: Капитан выбран", - cap_selected_desc="Ваш капитан - {}" + cap_selected_desc="Ваш капитан - {}", ), stats=StatsCommand( smbs_stats="Статистика {}", @@ -492,22 +474,22 @@ class Localization: games_won="Победы", winrate="Винрейт", note="Codenames - **командная игра**, поэтому статистика побед игрока **не может** точно отражать его " - "мастерство", - egg_game_master_desc="Лучший ведущий: **100%**" + "мастерство", + egg_game_master_desc="Лучший ведущий: **100%**", ), prefix=PrefixCommand( prefix_changed_title="Префикс изменен", prefix_changed_desc="{}\nСлеш-команды, " - "установленный по умолчанию **`cdn`** и пинг бота все также работают", + "установленный по умолчанию **`cdn`** и пинг бота все также работают", new_prefix="Новый префикс:\n**`{}`**\n", - prefix_deleted="Префикс сервера сброшен" + prefix_deleted="Префикс сервера сброшен", ), language=LanguageCommand( title="Настройки языка", desc_current="**Установленный язык: {} {}**\n\n_Выберите новый язык:_\n\n", desc_set="**Язык установлен на {} {}**", - desc_aborted="Отменено" - ) + desc_aborted="Отменено", + ), ), ui=UI( alert_title="Предупреждение", @@ -517,7 +499,7 @@ class Localization: random="Случайная команда", leave="Выйти", start_game="Начать игру", - cancel_reg="Отменить регистрацию" + cancel_reg="Отменить регистрацию", ), errors=Errors( title="Ошибка", @@ -529,7 +511,7 @@ class Localization: invalid_command="Такой команды не существует", not_enough_players="**Недостаточно игроков**\nВ **каждой** команде должно быть **хотя бы 2** игрока.", too_many_players="**Слишком много игроков**\nВ **каждой** команде должно быть **не более 25** игроков.", - never_played="{} еще не играл в Codenames" - ) - ) + never_played="{} еще не играл в Codenames", + ), + ), } diff --git a/misc/util.py b/misc/util.py index cb72873..7708c8c 100644 --- a/misc/util.py +++ b/misc/util.py @@ -1,7 +1,16 @@ import asyncio from typing import Iterable, Callable, Any -from discord import File, Embed, PartialMessageable, User, Message, Reaction, Interaction, ButtonStyle +from discord import ( + File, + Embed, + PartialMessageable, + User, + Message, + Reaction, + Interaction, + ButtonStyle, +) from discord.ext.commands import Parameter, Command, Context from discord.ui import View, Button, button @@ -55,7 +64,11 @@ def process_param(name: str, param: Parameter, slash: bool) -> str | None: if param.required: return f"<{param.name}>" - default = None if not param.default and not isinstance(param.default, bool) else param.default + default = ( + None + if not param.default and not isinstance(param.default, bool) + else param.default + ) default = f'"{default}"' if isinstance(default, str) else default return f"[{param.name}={f'{default}'}]" @@ -72,7 +85,9 @@ def if_command_has_check(command: Command, check: str) -> bool: return check in map(lambda check: check.__qualname__.split(".")[0], command.checks) -async def send_error(ctx: Context | Interaction | PartialMessageable, title: str, description: str) -> None: +async def send_error( + ctx: Context | Interaction | PartialMessageable, title: str, description: str +) -> None: """ Sends error message to the given context with given title and description @@ -82,11 +97,7 @@ async def send_error(ctx: Context | Interaction | PartialMessageable, title: str :return: None """ - error_embed = Embed( - title=title, - description=description, - color=Colors.red - ) + error_embed = Embed(title=title, description=description, color=Colors.red) if isinstance(ctx, Interaction): # called from ui await ctx.followup.send(embed=error_embed, ephemeral=True) @@ -101,7 +112,11 @@ async def send_error(ctx: Context | Interaction | PartialMessageable, title: str async def send_alert( - interaction: Interaction, loc: Localization, action: str, callback: Callable, *params: Any + interaction: Interaction, + loc: Localization, + action: str, + callback: Callable, + *params: Any, ) -> None: """ Sends an alert to the given :class:`Interaction` with the given action and callback @@ -118,10 +133,10 @@ async def send_alert( embed=Embed( title=loc.ui.alert_title, description=loc.ui.alert_desc.format(action), - color=Colors.purple + color=Colors.purple, ), view=AlertView(loc, callback, *params), - ephemeral=True + ephemeral=True, ) @@ -138,7 +153,9 @@ async def count_certain_reacted_users(reaction: Reaction, users: Iterable[User]) # noinspection PyTypeChecker -async def most_count_reaction_emojis(msg: Message, counted_users: Iterable[User]) -> tuple[str]: +async def most_count_reaction_emojis( + msg: Message, counted_users: Iterable[User] +) -> tuple[str]: """ Returns emojis that were reacted to the most by given users, for given :class:`Message` @@ -148,16 +165,24 @@ async def most_count_reaction_emojis(msg: Message, counted_users: Iterable[User] """ filtered_reactions = tuple(filter(lambda r: r.me, msg.reactions)) - counts = [await count_certain_reacted_users(r, counted_users) for r in filtered_reactions] - max_reactions = tuple(map( - lambda pair: pair[0], - filter(lambda pair: pair[1] == max(counts), zip(filtered_reactions, counts)) - )) + counts = [ + await count_certain_reacted_users(r, counted_users) for r in filtered_reactions + ] + max_reactions = tuple( + map( + lambda pair: pair[0], + filter( + lambda pair: pair[1] == max(counts), zip(filtered_reactions, counts) + ), + ) + ) return tuple(map(lambda r: r.emoji, max_reactions)) # noinspection PyUnboundLocalVariable -async def pros_and_cons(msg: Message, delay: float, counted_users: Iterable[User]) -> tuple[int, int]: +async def pros_and_cons( + msg: Message, delay: float, counted_users: Iterable[User] +) -> tuple[int, int]: """ Starts a "pros and cons" vote for the given :class:`Message` with the given delay to wait for users to react. @@ -173,7 +198,9 @@ async def pros_and_cons(msg: Message, delay: float, counted_users: Iterable[User await msg.add_reaction("👎") await asyncio.sleep(delay) - new_msg = await msg.channel.fetch_message(msg.id) # Have to get the message object again with reactions in it + new_msg = await msg.channel.fetch_message( + msg.id + ) # Have to get the message object again with reactions in it reactions = filter(lambda r: r.emoji in "👍👎", new_msg.reactions) for reaction in reactions: if reaction.emoji == "👍": @@ -185,7 +212,11 @@ async def pros_and_cons(msg: Message, delay: float, counted_users: Iterable[User async def send_fields( - game_uuid: str, channel: PartialMessageable, first_cap: User, second_cap: User, send_to_caps: bool = True + game_uuid: str, + channel: PartialMessageable, + first_cap: User, + second_cap: User, + send_to_caps: bool = True, ) -> None: """ Sends fields to the game text channel (player filed) and to the captains (captain field) @@ -201,5 +232,9 @@ async def send_fields( await channel.send(file=File(Paths.pl_img(game_uuid), filename="player_field.png")) if send_to_caps: - await first_cap.send(file=File(Paths.cap_img(game_uuid), filename="captain_field.png")) - await second_cap.send(file=File(Paths.cap_img(game_uuid), filename="captain_field.png")) + await first_cap.send( + file=File(Paths.cap_img(game_uuid), filename="captain_field.png") + ) + await second_cap.send( + file=File(Paths.cap_img(game_uuid), filename="captain_field.png") + ) diff --git a/requirements.txt b/requirements.txt index 59bc992..0e9b432 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,15 @@ -i https://pypi.org/simple -aiofiles==23.2.1; python_version >= '3.7' -aiohttp==3.9.1; python_version >= '3.8' +aiofiles==24.1.0; python_version >= '3.8' +aiohappyeyeballs==2.4.0; python_version >= '3.8' +aiohttp==3.10.5; python_version >= '3.8' aiosignal==1.3.1; python_version >= '3.7' -aiosqlite==0.19.0; python_version >= '3.7' -attrs==23.2.0; python_version >= '3.7' +aiosqlite==0.20.0; python_version >= '3.8' +attrs==24.2.0; python_version >= '3.7' discord==2.3.2 -discord.py==2.3.2; python_full_version >= '3.8.0' +discord.py==2.4.0; python_version >= '3.8' frozenlist==1.4.1; python_version >= '3.8' -idna==3.6; python_version >= '3.5' -multidict==6.0.4; python_version >= '3.7' -pillow==10.1.0; python_version >= '3.8' +idna==3.8; python_version >= '3.6' +multidict==6.0.5; python_version >= '3.7' +pillow==10.4.0; python_version >= '3.8' +typing-extensions==4.12.2; python_version >= '3.8' yarl==1.9.4; python_version >= '3.7'