Compare commits

..

82 Commits

Author SHA1 Message Date
Zed
3525ea42e1 Jack's Changes (#1)
First merge after adding branch

Co-authored-by: c0de <c0de@c0de.dev>
Reviewed-on: #1
Co-authored-by: Zed <benolken5@gmail.com>
Co-committed-by: Zed <benolken5@gmail.com>
2024-04-23 22:19:20 +00:00
f1db0f62fc Await discord user interaction with name get 2022-12-10 17:57:53 -06:00
fd5f5a7e7a I Completely disagree with this decision.
Remove the table for the original output
2022-12-10 12:36:36 -06:00
fc2fe0a396 update help 2022-12-10 12:31:56 -06:00
292db8133d clear total points 2022-12-10 12:31:19 -06:00
febdfaf355 Format points table with beautifultable 2022-12-09 23:20:54 -06:00
9702e1e77a Add reset command / module 2022-12-09 23:20:20 -06:00
ff5e7f310a Sort endgame table by difference 2022-12-09 23:15:28 -06:00
d7f4540dcd Install beautifultable 2022-12-09 22:44:04 -06:00
97a1ab1b2b disable too-few-public-methods pylint warning 2022-12-09 22:30:36 -06:00
43cfb49cd6 remove unused import 2022-12-09 22:28:51 -06:00
133af80aad Use BeautifulTable library to make nice tables :3 2022-12-09 22:28:37 -06:00
59d1dc4afb generate uuid on the model 2022-12-09 21:34:47 -06:00
7577e3afee generate the uuid on the model 2022-12-09 21:34:32 -06:00
ad70870d8e Update readme with correct information 2022-11-12 01:01:27 -06:00
a774d0a0fc It's baseball, not ghostball :) 2022-11-12 01:00:24 -06:00
512b3b05d2 Nedc to set difference to the class 2022-11-12 00:02:14 -06:00
0c4224314b Add ping to help message 2022-11-11 23:55:15 -06:00
1a3d2f0b14 update dockerfile 2022-11-11 21:29:05 -06:00
4318b58db0 remove comment 2022-11-11 21:23:17 -06:00
a32bb598d2 Get rid of original/reference code, move everything to root 2022-11-11 20:45:49 -06:00
dbaa692711 Add dockerfile 2022-11-11 20:16:36 -06:00
5557c16d8c Sync vscode settings because why not 2022-11-11 20:16:25 -06:00
b7bd032e07 format with black 2022-11-11 20:07:23 -06:00
16a419f558 Fix circular import issue 2022-11-11 20:06:14 -06:00
03fcc5ef52 Uninstall dateparser 2022-11-11 13:03:26 -06:00
5f70ffd69b Init before registering command 2022-11-11 12:52:54 -06:00
460a3e08d2 Remove comment 2022-11-10 22:45:16 -06:00
51092e52a2 Add extra whitespace 2022-11-10 22:44:24 -06:00
b067a6ca07 Use the new game manager 2022-11-10 22:43:49 -06:00
bcbb74a3e1 Create composit class of available commands 2022-11-10 22:43:40 -06:00
0ed4ca4034 Add process_guess (this is the old guess.py) 2022-11-10 22:43:19 -06:00
2d8dc471f5 Add help manager 2022-11-10 22:42:45 -06:00
1b53d230cb Add points manager 2022-11-10 22:42:38 -06:00
13eab14f17 Add guess manager 2022-11-10 22:42:30 -06:00
3e98b025c7 Add end game manager 2022-11-10 22:42:20 -06:00
f9a5a8023d Add new game manager 2022-11-10 22:42:08 -06:00
98de64b7aa Add new game manager 2022-11-10 22:41:42 -06:00
353f858bac Delete GameManager 2022-11-10 22:40:33 -06:00
e05f023c4e Update docstrings 2022-11-10 22:13:07 -06:00
7818109a9c Fix database query 2022-11-10 22:10:58 -06:00
49f2630e38 Save to database seperately from getting points 2022-11-10 22:10:31 -06:00
175b02536c Refactor init to what is passed and defaults 2022-11-10 22:09:36 -06:00
5cf6b30eb2 Remove check_is_running decorator since it didn't work 2022-11-10 21:59:49 -06:00
7cf006cd4f Add help command 2022-11-10 21:58:55 -06:00
5131c4a3c1 linting 2022-11-10 21:46:42 -06:00
fbf588a85e Show table of points 2022-11-10 21:46:18 -06:00
bf8819fb46 Show total difference 2022-11-10 21:45:45 -06:00
946d826dc4 Only update the guess for the current game 2022-11-10 21:05:21 -06:00
83b6bda124 Linting changes 2022-11-10 21:03:38 -06:00
e68ef52292 Add calculation for the difference score 2022-11-10 21:02:20 -06:00
3435e4ce7a Fix to the point of being runnable 2022-11-09 11:48:31 -06:00
eb5ed5fe9e update readme 2022-10-30 22:56:41 -05:00
e39a19da55 [refactor] Processing guesses moved to own class 2022-10-30 22:24:51 -05:00
f6860bb43f maybe, maybe not 2022-10-27 00:18:12 -05:00
3c5fead8b0 Allow the bot to see channel messages 2022-10-27 00:04:54 -05:00
b6377aef97 no change database path 2022-10-26 23:53:46 -05:00
9a39399936 Don't need to pass game manager instance 2022-10-26 23:37:23 -05:00
65bc29cfa1 Refactor and get the game to run (in DMs) 2022-10-26 23:36:41 -05:00
b0c6b8e5bb Create database before starting the client 2022-10-26 23:35:52 -05:00
e9d80e5e62 Add PlayerModel to create_models 2022-10-25 23:18:52 -05:00
1a62747da1 Difference code in place 2022-10-25 23:17:57 -05:00
efe58e0278 Add Player Model for total points 2022-10-25 23:17:06 -05:00
84e4975d27 The game is GameManager now 2022-10-25 23:16:33 -05:00
3dfbb247da Change guess update logic 2022-10-25 19:23:54 -05:00
f4bd33b4d5 Fix linting on Game
Also default guess value to 0
2022-10-24 20:53:51 -05:00
545e0442eb Fix linting on database models (except todo) 2022-10-24 20:31:47 -05:00
e0dc0e6b78 Fix linting issues with client 2022-10-24 20:22:04 -05:00
b154efb6cc Fix linting issues with main 2022-10-24 20:06:50 -05:00
57a808f19d Create lint log 2022-10-24 20:05:21 -05:00
b32d76bf16 The decorator should be outside of the class 2022-10-24 20:02:26 -05:00
07ecd2248c Format with Black 2022-10-24 20:00:39 -05:00
7d684d8b9d Install pylint and black
for formatting and ensuring the code conforms to PEP standards
2022-10-24 19:59:18 -05:00
1845e42488 Use the with statement on the game class
This is because the class is a context manager / state machine
2022-10-24 19:55:50 -05:00
7101db5394 We'll be fine with one database connection, and don't have to repeat ourselves 2022-10-24 19:13:34 -05:00
49d3672936 Add extra time tracking 2022-10-24 19:12:48 -05:00
8ab3cd611f These need to be named properly 2022-10-24 18:49:26 -05:00
e771ea8764 Ignore databases 2022-10-24 18:47:12 -05:00
321fa9ae7c It's an Integer 2022-10-24 18:43:38 -05:00
59b8c673e0 Use correct model class 2022-10-03 20:08:42 -05:00
311dcac071 Fix import paths 2022-10-03 20:08:27 -05:00
b95e5d6bad Install dateparser 2022-10-03 20:07:01 -05:00
39 changed files with 1403 additions and 1218 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
.idea/*
__pycache__/
*.db
*.log

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"python.linting.pylintEnabled": true,
"python.linting.enabled": true,
"python.formatting.provider": "black"
}

18
Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
# Build with: docker build -t baseballbot:<version> .
# Run with: docker run -it
FROM python:3.10-alpine3.16 AS build
RUN pip install --no-cache-dir discord peewee
WORKDIR /app
COPY . .
FROM build AS run
ENV discord_token ""
ENV database_path "/tmp/baseball.db"
CMD ["python", "-u", "/app/main.py"]

View File

@@ -1,372 +0,0 @@
{
"_meta": {
"hash": {
"sha256": "303ecac922fdd13c07f0c9befb6ae764f1ed4694ead21054771034d016c21b42"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.10"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"aiohttp": {
"hashes": [
"sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8",
"sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142",
"sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18",
"sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34",
"sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a",
"sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033",
"sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06",
"sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4",
"sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d",
"sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b",
"sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc",
"sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091",
"sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d",
"sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85",
"sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb",
"sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937",
"sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf",
"sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1",
"sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b",
"sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d",
"sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269",
"sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da",
"sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346",
"sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494",
"sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697",
"sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4",
"sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585",
"sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c",
"sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da",
"sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad",
"sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2",
"sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6",
"sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c",
"sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849",
"sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa",
"sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b",
"sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb",
"sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7",
"sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715",
"sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76",
"sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d",
"sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276",
"sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6",
"sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37",
"sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb",
"sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d",
"sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c",
"sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446",
"sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008",
"sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342",
"sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d",
"sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7",
"sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061",
"sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba",
"sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7",
"sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290",
"sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0",
"sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d",
"sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8",
"sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f",
"sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48",
"sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502",
"sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62",
"sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9",
"sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403",
"sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77",
"sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476",
"sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e",
"sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96",
"sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5",
"sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784",
"sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091",
"sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b",
"sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97",
"sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a",
"sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2",
"sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9",
"sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d",
"sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73",
"sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017",
"sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363",
"sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c",
"sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d",
"sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618",
"sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491",
"sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b",
"sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca"
],
"markers": "python_version >= '3.6'",
"version": "==3.8.3"
},
"aiosignal": {
"hashes": [
"sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a",
"sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"
],
"markers": "python_version >= '3.6'",
"version": "==1.2.0"
},
"async-timeout": {
"hashes": [
"sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15",
"sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"
],
"markers": "python_version >= '3.6'",
"version": "==4.0.2"
},
"attrs": {
"hashes": [
"sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6",
"sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"
],
"markers": "python_version >= '3.5'",
"version": "==22.1.0"
},
"charset-normalizer": {
"hashes": [
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
],
"markers": "python_version >= '3.6'",
"version": "==2.1.1"
},
"discord": {
"hashes": [
"sha256:b6df1dd56c19750b3cb9b69de85bf463e712a0db232546ae8109c04bf0a61083",
"sha256:ffc714978d338d2b506e4924d66d7d02a649378a46743e2bdf42ef1bd43d1a67"
],
"index": "pypi",
"version": "==2.0.0"
},
"discord.py": {
"hashes": [
"sha256:309146476e986cb8faf038cd5d604d4b3834ef15c2d34df697ce5064bf5cd779",
"sha256:aeb186348bf011708b085b2715cf92bbb72c692eb4f59c4c0b488130cc4c4b7e"
],
"markers": "python_full_version >= '3.8.0'",
"version": "==2.0.1"
},
"frozenlist": {
"hashes": [
"sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e",
"sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04",
"sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944",
"sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845",
"sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f",
"sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f",
"sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb",
"sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2",
"sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3",
"sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f",
"sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a",
"sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131",
"sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1",
"sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa",
"sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8",
"sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b",
"sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2",
"sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e",
"sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b",
"sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b",
"sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10",
"sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170",
"sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8",
"sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b",
"sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989",
"sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd",
"sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03",
"sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519",
"sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189",
"sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b",
"sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca",
"sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff",
"sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96",
"sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d",
"sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5",
"sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2",
"sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204",
"sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a",
"sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8",
"sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141",
"sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792",
"sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9",
"sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c",
"sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c",
"sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221",
"sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d",
"sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc",
"sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f",
"sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9",
"sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef",
"sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3",
"sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab",
"sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b",
"sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db",
"sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988",
"sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6",
"sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc",
"sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83",
"sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be"
],
"markers": "python_version >= '3.7'",
"version": "==1.3.1"
},
"idna": {
"hashes": [
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
"sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
],
"markers": "python_version >= '3.5'",
"version": "==3.4"
},
"multidict": {
"hashes": [
"sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60",
"sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c",
"sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672",
"sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51",
"sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032",
"sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2",
"sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b",
"sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80",
"sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88",
"sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a",
"sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d",
"sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389",
"sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c",
"sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9",
"sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c",
"sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516",
"sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b",
"sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43",
"sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee",
"sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227",
"sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d",
"sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae",
"sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7",
"sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4",
"sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9",
"sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f",
"sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013",
"sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9",
"sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e",
"sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693",
"sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a",
"sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15",
"sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb",
"sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96",
"sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87",
"sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376",
"sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658",
"sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0",
"sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071",
"sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360",
"sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc",
"sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3",
"sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba",
"sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8",
"sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9",
"sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2",
"sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3",
"sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68",
"sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8",
"sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d",
"sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49",
"sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608",
"sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57",
"sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86",
"sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20",
"sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293",
"sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849",
"sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937",
"sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"
],
"markers": "python_version >= '3.7'",
"version": "==6.0.2"
},
"peewee": {
"hashes": [
"sha256:cc934286d0c0842203abe66a3c6583d1463371e633b03d6da054d0f74e70706f"
],
"index": "pypi",
"version": "==3.15.3"
},
"yarl": {
"hashes": [
"sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb",
"sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3",
"sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035",
"sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453",
"sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d",
"sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a",
"sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231",
"sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f",
"sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae",
"sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b",
"sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3",
"sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507",
"sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd",
"sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae",
"sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe",
"sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c",
"sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4",
"sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64",
"sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357",
"sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54",
"sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461",
"sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4",
"sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497",
"sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0",
"sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1",
"sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957",
"sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350",
"sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780",
"sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843",
"sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548",
"sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6",
"sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40",
"sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee",
"sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b",
"sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6",
"sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0",
"sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e",
"sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880",
"sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc",
"sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e",
"sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead",
"sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28",
"sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf",
"sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd",
"sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae",
"sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0",
"sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0",
"sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae",
"sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda",
"sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546",
"sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802",
"sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be",
"sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07",
"sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936",
"sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272",
"sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc",
"sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a",
"sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28",
"sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b"
],
"markers": "python_version >= '3.7'",
"version": "==1.8.1"
}
},
"develop": {}
}

View File

@@ -1,44 +0,0 @@
#!/usr/bin/env python3
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
import os
import datetime
from peewee import *
# User can provide path to database, or it will be put next to models.py
DATABASE = os.environ.get('database_path', os.getcwd() + '/ghostball.db')
database = SqliteDatabase(DATABASE)
class BaseModel(Model):
"""All of our models will inherit this class
and use the same database connection"""
class Meta:
database = database
class GameModel(BaseModel):
game_id = UUIDField(primary_key=True)
server_id = UUIDField() # Unsure if this is actually a uuid
pitch_value = IntegerField(null=True)
date_created = DateTimeField(default=datetime.datetime.now)
class GuessModel(BaseModel):
player_id = IntegerField(primary_key=True)
game_id = ForeignKeyField(Game, backref="guesses")
player_name = CharField()
guess = IntegerField()
difference = IntegerField(null=True)
# TODO: Add unique constraint for player_id and game_id
# ie: one guess per player allowed per game
def create_models():
with database:
database.create_tables([Play, Guess])

View File

@@ -1,137 +0,0 @@
#!/usr/bin/env python3
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
import uuid
import dateparser
from .database.models import database, GameModel, GuessModel
class Game:
"""
The game state class
This represents a game that exists in a channel
"""
def __init__(self):
# Only one game should run at at time
self.is_running = False
self.commands = {
'ghostball': self.start,
'resolve': self.stop,
'guess': self.guess,
'points': self.points,
'help': self.help,
}
self.game = GameModel
# Discord message
self.message = None
# Discord client instance
self.discord = None
async def start(self):
if self.is_running:
return await self.message.channel.send("A game is already running")
database.connect()
self.is_running = True
# game.pitch_value is unknown at the start of the game
self.game = GameModel.create(
game_id = uuid.uuid4(),
server_id = self.message.guild.id
)
database.close()
await self.message.send("@flappy ball, pitch is in! Send me your guesses with !guess <number>")
def __stopArgs__(self):
pieces = self.message.content.split()
if len(pieces) == 2:
return pieces[1], False, None, None
elif len(pieces) == 4:
return pieces[1], True, pieces[2], pieces[3]
return None, False, None, None
async def stop(self):
if not self.is_running:
return await self.message.channel.send("There is no game running to resolve")
# Determine arguments
pitch_value, has_batter, batter_id, batter_guess = self.__stopArgs__()
if not pitch_value:
return await self.message.channel.send(f"Invalid command <@{ str(self.message.author.id) }>!")
database.connect()
if has_batter:
player_id = batter_id[3:]
GuessModel.create(
game_id = self.game.game_id,
player_id = player_id,
player_name = self.discord.get_user(int(player_id).name),
guess = int(batter_guess)
)
# Save the pitch value
self.game.update({'pitch_value': pitch_value})
# TODO: Determine differences
await self.message.channel.send("Difference calculation is not currently available")
# stop and discard game
self.is_running = False
self.game = None
database.close()
async def guess(self):
if not self.is_running:
return await self.message.channel.send("There is no game running to add guesses to")
value = int(self.message.content.split()[1])
if value < 1 or value > 1000:
return await self.message.channel.send(f"Invalid value. It must be between 1 and 1000 inclusive")
database.connect()
GuessModel.create(
game_id = self.game.game_id,
player_id = self.message.author.id,
player_name = self.message.author.name,
guess = value
)
database.close()
return await self.message.add_reaction(emoji="\N{THUMBS UP SIGN}")
async def points(self):
# database.connect()
value = self.message.content.split()
try:
if len(value) > 1:
timestamp = dateparser.parse(value[1])
except:
return await self.message.channel.send("Invalid timestamp. Try again")
# database.close()
return await self.message.channel.send("Sorry, not implemented yet")
async def help(self):
# TODO: Add help message
help_message = "help"
recipient = await self.discord.fetch_user(self.message.author.id)
await recipient.send(help_message)

View File

@@ -6,6 +6,8 @@ name = "pypi"
[packages]
discord = "*"
peewee = "*"
pylint = "*"
black = "*"
[dev-packages]

617
Pipfile.lock generated Normal file
View File

@@ -0,0 +1,617 @@
{
"_meta": {
"hash": {
"sha256": "b6fb0d9cfe588eab84db4024e6b319681e9e70d00c74282b6722fe283ac3e348"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.10"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"aiohttp": {
"hashes": [
"sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8",
"sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142",
"sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18",
"sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34",
"sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a",
"sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033",
"sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06",
"sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4",
"sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d",
"sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b",
"sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc",
"sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091",
"sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d",
"sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85",
"sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb",
"sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937",
"sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf",
"sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1",
"sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b",
"sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d",
"sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269",
"sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da",
"sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346",
"sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494",
"sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697",
"sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4",
"sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585",
"sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c",
"sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da",
"sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad",
"sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2",
"sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6",
"sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c",
"sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849",
"sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa",
"sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b",
"sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb",
"sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7",
"sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715",
"sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76",
"sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d",
"sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276",
"sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6",
"sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37",
"sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb",
"sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d",
"sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c",
"sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446",
"sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008",
"sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342",
"sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d",
"sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7",
"sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061",
"sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba",
"sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7",
"sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290",
"sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0",
"sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d",
"sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8",
"sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f",
"sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48",
"sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502",
"sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62",
"sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9",
"sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403",
"sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77",
"sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476",
"sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e",
"sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96",
"sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5",
"sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784",
"sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091",
"sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b",
"sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97",
"sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a",
"sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2",
"sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9",
"sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d",
"sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73",
"sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017",
"sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363",
"sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c",
"sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d",
"sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618",
"sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491",
"sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b",
"sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca"
],
"markers": "python_full_version >= '3.6.0'",
"version": "==3.8.3"
},
"aiosignal": {
"hashes": [
"sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc",
"sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"
],
"markers": "python_version >= '3.7'",
"version": "==1.3.1"
},
"astroid": {
"hashes": [
"sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907",
"sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7"
],
"markers": "python_full_version >= '3.7.2'",
"version": "==2.12.13"
},
"async-timeout": {
"hashes": [
"sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15",
"sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"
],
"markers": "python_full_version >= '3.6.0'",
"version": "==4.0.2"
},
"attrs": {
"hashes": [
"sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6",
"sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"
],
"markers": "python_version >= '3.5'",
"version": "==22.1.0"
},
"black": {
"hashes": [
"sha256:101c69b23df9b44247bd88e1d7e90154336ac4992502d4197bdac35dd7ee3320",
"sha256:159a46a4947f73387b4d83e87ea006dbb2337eab6c879620a3ba52699b1f4351",
"sha256:1f58cbe16dfe8c12b7434e50ff889fa479072096d79f0a7f25e4ab8e94cd8350",
"sha256:229351e5a18ca30f447bf724d007f890f97e13af070bb6ad4c0a441cd7596a2f",
"sha256:436cc9167dd28040ad90d3b404aec22cedf24a6e4d7de221bec2730ec0c97bcf",
"sha256:559c7a1ba9a006226f09e4916060982fd27334ae1998e7a38b3f33a37f7a2148",
"sha256:7412e75863aa5c5411886804678b7d083c7c28421210180d67dfd8cf1221e1f4",
"sha256:77d86c9f3db9b1bf6761244bc0b3572a546f5fe37917a044e02f3166d5aafa7d",
"sha256:82d9fe8fee3401e02e79767016b4907820a7dc28d70d137eb397b92ef3cc5bfc",
"sha256:9eedd20838bd5d75b80c9f5487dbcb06836a43833a37846cf1d8c1cc01cef59d",
"sha256:c116eed0efb9ff870ded8b62fe9f28dd61ef6e9ddd28d83d7d264a38417dcee2",
"sha256:d30b212bffeb1e252b31dd269dfae69dd17e06d92b87ad26e23890f3efea366f"
],
"index": "pypi",
"version": "==22.12.0"
},
"charset-normalizer": {
"hashes": [
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
],
"markers": "python_full_version >= '3.6.0'",
"version": "==2.1.1"
},
"click": {
"hashes": [
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.3"
},
"dill": {
"hashes": [
"sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0",
"sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373"
],
"markers": "python_version >= '3.7'",
"version": "==0.3.6"
},
"discord": {
"hashes": [
"sha256:3f479fcab569c621528c0190092d6383b4da13ec9631711ce3c14f33aedff4ff",
"sha256:44515e6d02e61528b2a81e511e03b3f60ce0b3737ba9efc69c2defce2f1ebc1d"
],
"index": "pypi",
"version": "==2.1.0"
},
"discord.py": {
"hashes": [
"sha256:027ccdd22b5bb66a9e19cbd8daa1bc74b49271a16a074d57e52f288fcfa208e8",
"sha256:a2cfa9f09e3013aaaa43600cc8dfaf67c532dd34afcb71e550f5a0dc9133a5e0"
],
"markers": "python_full_version >= '3.8.0'",
"version": "==2.1.0"
},
"frozenlist": {
"hashes": [
"sha256:008a054b75d77c995ea26629ab3a0c0d7281341f2fa7e1e85fa6153ae29ae99c",
"sha256:02c9ac843e3390826a265e331105efeab489ffaf4dd86384595ee8ce6d35ae7f",
"sha256:034a5c08d36649591be1cbb10e09da9f531034acfe29275fc5454a3b101ce41a",
"sha256:05cdb16d09a0832eedf770cb7bd1fe57d8cf4eaf5aced29c4e41e3f20b30a784",
"sha256:0693c609e9742c66ba4870bcee1ad5ff35462d5ffec18710b4ac89337ff16e27",
"sha256:0771aed7f596c7d73444c847a1c16288937ef988dc04fb9f7be4b2aa91db609d",
"sha256:0af2e7c87d35b38732e810befb9d797a99279cbb85374d42ea61c1e9d23094b3",
"sha256:14143ae966a6229350021384870458e4777d1eae4c28d1a7aa47f24d030e6678",
"sha256:180c00c66bde6146a860cbb81b54ee0df350d2daf13ca85b275123bbf85de18a",
"sha256:1841e200fdafc3d51f974d9d377c079a0694a8f06de2e67b48150328d66d5483",
"sha256:23d16d9f477bb55b6154654e0e74557040575d9d19fe78a161bd33d7d76808e8",
"sha256:2b07ae0c1edaa0a36339ec6cce700f51b14a3fc6545fdd32930d2c83917332cf",
"sha256:2c926450857408e42f0bbc295e84395722ce74bae69a3b2aa2a65fe22cb14b99",
"sha256:2e24900aa13212e75e5b366cb9065e78bbf3893d4baab6052d1aca10d46d944c",
"sha256:303e04d422e9b911a09ad499b0368dc551e8c3cd15293c99160c7f1f07b59a48",
"sha256:352bd4c8c72d508778cf05ab491f6ef36149f4d0cb3c56b1b4302852255d05d5",
"sha256:3843f84a6c465a36559161e6c59dce2f2ac10943040c2fd021cfb70d58c4ad56",
"sha256:394c9c242113bfb4b9aa36e2b80a05ffa163a30691c7b5a29eba82e937895d5e",
"sha256:3bbdf44855ed8f0fbcd102ef05ec3012d6a4fd7c7562403f76ce6a52aeffb2b1",
"sha256:40de71985e9042ca00b7953c4f41eabc3dc514a2d1ff534027f091bc74416401",
"sha256:41fe21dc74ad3a779c3d73a2786bdf622ea81234bdd4faf90b8b03cad0c2c0b4",
"sha256:47df36a9fe24054b950bbc2db630d508cca3aa27ed0566c0baf661225e52c18e",
"sha256:4ea42116ceb6bb16dbb7d526e242cb6747b08b7710d9782aa3d6732bd8d27649",
"sha256:58bcc55721e8a90b88332d6cd441261ebb22342e238296bb330968952fbb3a6a",
"sha256:5c11e43016b9024240212d2a65043b70ed8dfd3b52678a1271972702d990ac6d",
"sha256:5cf820485f1b4c91e0417ea0afd41ce5cf5965011b3c22c400f6d144296ccbc0",
"sha256:5d8860749e813a6f65bad8285a0520607c9500caa23fea6ee407e63debcdbef6",
"sha256:6327eb8e419f7d9c38f333cde41b9ae348bec26d840927332f17e887a8dcb70d",
"sha256:65a5e4d3aa679610ac6e3569e865425b23b372277f89b5ef06cf2cdaf1ebf22b",
"sha256:66080ec69883597e4d026f2f71a231a1ee9887835902dbe6b6467d5a89216cf6",
"sha256:783263a4eaad7c49983fe4b2e7b53fa9770c136c270d2d4bbb6d2192bf4d9caf",
"sha256:7f44e24fa70f6fbc74aeec3e971f60a14dde85da364aa87f15d1be94ae75aeef",
"sha256:7fdfc24dcfce5b48109867c13b4cb15e4660e7bd7661741a391f821f23dfdca7",
"sha256:810860bb4bdce7557bc0febb84bbd88198b9dbc2022d8eebe5b3590b2ad6c842",
"sha256:841ea19b43d438a80b4de62ac6ab21cfe6827bb8a9dc62b896acc88eaf9cecba",
"sha256:84610c1502b2461255b4c9b7d5e9c48052601a8957cd0aea6ec7a7a1e1fb9420",
"sha256:899c5e1928eec13fd6f6d8dc51be23f0d09c5281e40d9cf4273d188d9feeaf9b",
"sha256:8bae29d60768bfa8fb92244b74502b18fae55a80eac13c88eb0b496d4268fd2d",
"sha256:8df3de3a9ab8325f94f646609a66cbeeede263910c5c0de0101079ad541af332",
"sha256:8fa3c6e3305aa1146b59a09b32b2e04074945ffcfb2f0931836d103a2c38f936",
"sha256:924620eef691990dfb56dc4709f280f40baee568c794b5c1885800c3ecc69816",
"sha256:9309869032abb23d196cb4e4db574232abe8b8be1339026f489eeb34a4acfd91",
"sha256:9545a33965d0d377b0bc823dcabf26980e77f1b6a7caa368a365a9497fb09420",
"sha256:9ac5995f2b408017b0be26d4a1d7c61bce106ff3d9e3324374d66b5964325448",
"sha256:9bbbcedd75acdfecf2159663b87f1bb5cfc80e7cd99f7ddd9d66eb98b14a8411",
"sha256:a4ae8135b11652b08a8baf07631d3ebfe65a4c87909dbef5fa0cdde440444ee4",
"sha256:a6394d7dadd3cfe3f4b3b186e54d5d8504d44f2d58dcc89d693698e8b7132b32",
"sha256:a97b4fe50b5890d36300820abd305694cb865ddb7885049587a5678215782a6b",
"sha256:ae4dc05c465a08a866b7a1baf360747078b362e6a6dbeb0c57f234db0ef88ae0",
"sha256:b1c63e8d377d039ac769cd0926558bb7068a1f7abb0f003e3717ee003ad85530",
"sha256:b1e2c1185858d7e10ff045c496bbf90ae752c28b365fef2c09cf0fa309291669",
"sha256:b4395e2f8d83fbe0c627b2b696acce67868793d7d9750e90e39592b3626691b7",
"sha256:b756072364347cb6aa5b60f9bc18e94b2f79632de3b0190253ad770c5df17db1",
"sha256:ba64dc2b3b7b158c6660d49cdb1d872d1d0bf4e42043ad8d5006099479a194e5",
"sha256:bed331fe18f58d844d39ceb398b77d6ac0b010d571cba8267c2e7165806b00ce",
"sha256:c188512b43542b1e91cadc3c6c915a82a5eb95929134faf7fd109f14f9892ce4",
"sha256:c21b9aa40e08e4f63a2f92ff3748e6b6c84d717d033c7b3438dd3123ee18f70e",
"sha256:ca713d4af15bae6e5d79b15c10c8522859a9a89d3b361a50b817c98c2fb402a2",
"sha256:cd4210baef299717db0a600d7a3cac81d46ef0e007f88c9335db79f8979c0d3d",
"sha256:cfe33efc9cb900a4c46f91a5ceba26d6df370ffddd9ca386eb1d4f0ad97b9ea9",
"sha256:d5cd3ab21acbdb414bb6c31958d7b06b85eeb40f66463c264a9b343a4e238642",
"sha256:dfbac4c2dfcc082fcf8d942d1e49b6aa0766c19d3358bd86e2000bf0fa4a9cf0",
"sha256:e235688f42b36be2b6b06fc37ac2126a73b75fb8d6bc66dd632aa35286238703",
"sha256:eb82dbba47a8318e75f679690190c10a5e1f447fbf9df41cbc4c3afd726d88cb",
"sha256:ebb86518203e12e96af765ee89034a1dbb0c3c65052d1b0c19bbbd6af8a145e1",
"sha256:ee78feb9d293c323b59a6f2dd441b63339a30edf35abcb51187d2fc26e696d13",
"sha256:eedab4c310c0299961ac285591acd53dc6723a1ebd90a57207c71f6e0c2153ab",
"sha256:efa568b885bca461f7c7b9e032655c0c143d305bf01c30caf6db2854a4532b38",
"sha256:efce6ae830831ab6a22b9b4091d411698145cb9b8fc869e1397ccf4b4b6455cb",
"sha256:f163d2fd041c630fed01bc48d28c3ed4a3b003c00acd396900e11ee5316b56bb",
"sha256:f20380df709d91525e4bee04746ba612a4df0972c1b8f8e1e8af997e678c7b81",
"sha256:f30f1928162e189091cf4d9da2eac617bfe78ef907a761614ff577ef4edfb3c8",
"sha256:f470c92737afa7d4c3aacc001e335062d582053d4dbe73cda126f2d7031068dd",
"sha256:ff8bf625fe85e119553b5383ba0fb6aa3d0ec2ae980295aaefa552374926b3f4"
],
"markers": "python_version >= '3.7'",
"version": "==1.3.3"
},
"idna": {
"hashes": [
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
"sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
],
"markers": "python_version >= '3.5'",
"version": "==3.4"
},
"isort": {
"hashes": [
"sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7",
"sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"
],
"markers": "python_version < '4.0' and python_full_version >= '3.6.1'",
"version": "==5.10.1"
},
"lazy-object-proxy": {
"hashes": [
"sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada",
"sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d",
"sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7",
"sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe",
"sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd",
"sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c",
"sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858",
"sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288",
"sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec",
"sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f",
"sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891",
"sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c",
"sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25",
"sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156",
"sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8",
"sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f",
"sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e",
"sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0",
"sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b"
],
"markers": "python_version >= '3.7'",
"version": "==1.8.0"
},
"mccabe": {
"hashes": [
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
],
"markers": "python_full_version >= '3.6.0'",
"version": "==0.7.0"
},
"multidict": {
"hashes": [
"sha256:018c8e3be7f161a12b3e41741b6721f9baeb2210f4ab25a6359b7d76c1017dce",
"sha256:01b456046a05ff7cceefb0e1d2a9d32f05efcb1c7e0d152446304e11557639ce",
"sha256:114a4ab3e5cfbc56c4b6697686ecb92376c7e8c56893ef20547921552f8bdf57",
"sha256:12e0d396faa6dc55ff5379eee54d1df3b508243ff15bfc8295a6ec7a4483a335",
"sha256:190626ced82d4cc567a09e7346340d380154a493bac6905e0095d8158cdf1e38",
"sha256:1f5d5129a937af4e3c4a1d6c139f4051b7d17d43276cefdd8d442a7031f7eef2",
"sha256:21e1ce0b187c4e93112304dcde2aa18922fdbe8fb4f13d8aa72a5657bce0563a",
"sha256:24e8d513bfcaadc1f8b0ebece3ff50961951c54b07d5a775008a882966102418",
"sha256:2523a29006c034687eccd3ee70093a697129a3ffe8732535d3b2df6a4ecc279d",
"sha256:26fbbe17f8a7211b623502d2bf41022a51da3025142401417c765bf9a56fed4c",
"sha256:2b66d61966b12e6bba500e5cbb2c721a35e119c30ee02495c5629bd0e91eea30",
"sha256:2cf5d19e12eff855aa198259c0b02fd3f5d07e1291fbd20279c37b3b0e6c9852",
"sha256:2cfda34b7cb99eacada2072e0f69c0ad3285cb6f8e480b11f2b6d6c1c6f92718",
"sha256:3541882266247c7cd3dba78d6ef28dbe704774df60c9e4231edaa4493522e614",
"sha256:36df958b15639e40472adaa4f0c2c7828fe680f894a6b48c4ce229f59a6a798b",
"sha256:38d394814b39be1c36ac709006d39d50d72a884f9551acd9c8cc1ffae3fc8c4e",
"sha256:4159fc1ec9ede8ab93382e0d6ba9b1b3d23c72da39a834db7a116986605c7ab4",
"sha256:445c0851a1cbc1f2ec3b40bc22f9c4a235edb3c9a0906122a9df6ea8d51f886c",
"sha256:47defc0218682281a52fb1f6346ebb8b68b17538163a89ea24dfe4da37a8a9a3",
"sha256:4cc5c8cd205a9810d16a5cd428cd81bac554ad1477cb87f4ad722b10992e794d",
"sha256:4ccf55f28066b4f08666764a957c2b7c241c7547b0921d69c7ceab5f74fe1a45",
"sha256:4fb3fe591956d8841882c463f934c9f7485cfd5f763a08c0d467b513dc18ef89",
"sha256:526f8397fc124674b8f39748680a0ff673bd6a715fecb4866716d36e380f015f",
"sha256:578bfcb16f4b8675ef71b960c00f174b0426e0eeb796bab6737389d8288eb827",
"sha256:5b51969503709415a35754954c2763f536a70b8bf7360322b2edb0c0a44391f6",
"sha256:5e58ec0375803526d395f6f7e730ecc45d06e15f68f7b9cdbf644a2918324e51",
"sha256:62db44727d0befea68e8ad2881bb87a9cfb6b87d45dd78609009627167f37b69",
"sha256:67090b17a0a5be5704fd109f231ee73cefb1b3802d41288d6378b5df46ae89ba",
"sha256:6cd14e61f0da2a2cfb9fe05bfced2a1ed7063ce46a7a8cd473be4973de9a7f91",
"sha256:70740c2bc9ab1c99f7cdcb104f27d16c63860c56d51c5bf0ef82fc1d892a2131",
"sha256:73009ea04205966d47e16d98686ac5c438af23a1bb30b48a2c5da3423ec9ce37",
"sha256:791458a1f7d1b4ab3bd9e93e0dcd1d59ef7ee9aa051dcd1ea030e62e49b923fd",
"sha256:7f9511e48bde6b995825e8d35e434fc96296cf07a25f4aae24ff9162be7eaa46",
"sha256:81c3d597591b0940e04949e4e4f79359b2d2e542a686ba0da5e25de33fec13e0",
"sha256:8230a39bae6c2e8a09e4da6bace5064693b00590a4a213e38f9a9366da10e7dd",
"sha256:8b92a9f3ab904397a33b193000dc4de7318ea175c4c460a1e154c415f9008e3d",
"sha256:94cbe5535ef150546b8321aebea22862a3284da51e7b55f6f95b7d73e96d90ee",
"sha256:960ce1b790952916e682093788696ef7e33ac6a97482f9b983abdc293091b531",
"sha256:99341ca1f1db9e7f47914cb2461305665a662383765ced6f843712564766956d",
"sha256:9aac6881454a750554ed4b280a839dcf9e2133a9d12ab4d417d673fb102289b7",
"sha256:9d359b0a962e052b713647ac1f13eabf2263167b149ed1e27d5c579f5c8c7d2c",
"sha256:9dbab2a7e9c073bc9538824a01f5ed689194db7f55f2b8102766873e906a6c1a",
"sha256:a27b029caa3b555a4f3da54bc1e718eb55fcf1a11fda8bf0132147b476cf4c08",
"sha256:a8b817d4ed68fd568ec5e45dd75ddf30cc72a47a6b41b74d5bb211374c296f5e",
"sha256:ad7d66422b9cc51125509229693d27e18c08f2dea3ac9de408d821932b1b3759",
"sha256:b46e79a9f4db53897d17bc64a39d1c7c2be3e3d4f8dba6d6730a2b13ddf0f986",
"sha256:baa96a3418e27d723064854143b2f414a422c84cc87285a71558722049bebc5a",
"sha256:beeca903e4270b4afcd114f371a9602240dc143f9e944edfea00f8d4ad56c40d",
"sha256:c2a1168e5aa7c72499fb03c850e0f03f624fa4a5c8d2e215c518d0a73872eb64",
"sha256:c5790cc603456b6dcf8a9a4765f666895a6afddc88b3d3ba7b53dea2b6e23116",
"sha256:cb4a08f0aaaa869f189ffea0e17b86ad0237b51116d494da15ef7991ee6ad2d7",
"sha256:cd5771e8ea325f85cbb361ddbdeb9ae424a68e5dfb6eea786afdcd22e68a7d5d",
"sha256:ce8e51774eb03844588d3c279adb94efcd0edeccd2f97516623292445bcc01f9",
"sha256:d09daf5c6ce7fc6ed444c9339bbde5ea84e2534d1ca1cd37b60f365c77f00dea",
"sha256:d0e798b072cf2aab9daceb43d97c9c527a0c7593e67a7846ad4cc6051de1e303",
"sha256:d325d61cac602976a5d47b19eaa7d04e3daf4efce2164c630219885087234102",
"sha256:d408172519049e36fb6d29672f060dc8461fc7174eba9883c7026041ef9bfb38",
"sha256:d52442e7c951e4c9ee591d6047706e66923d248d83958bbf99b8b19515fffaef",
"sha256:dc4cfef5d899f5f1a15f3d2ac49f71107a01a5a2745b4dd53fa0cede1419385a",
"sha256:df7b4cee3ff31b3335aba602f8d70dbc641e5b7164b1e9565570c9d3c536a438",
"sha256:e068dfeadbce63072b2d8096486713d04db4946aad0a0f849bd4fc300799d0d3",
"sha256:e07c24018986fb00d6e7eafca8fcd6e05095649e17fcf0e33a592caaa62a78b9",
"sha256:e0bce9f7c30e7e3a9e683f670314c0144e8d34be6b7019e40604763bd278d84f",
"sha256:e1925f78a543b94c3d46274c66a366fee8a263747060220ed0188e5f3eeea1c0",
"sha256:e322c94596054352f5a02771eec71563c018b15699b961aba14d6dd943367022",
"sha256:e4a095e18847c12ec20e55326ab8782d9c2d599400a3a2f174fab4796875d0e2",
"sha256:e5a811aab1b4aea0b4be669363c19847a8c547510f0e18fb632956369fdbdf67",
"sha256:eddf604a3de2ace3d9a4e4d491be7562a1ac095a0a1c95a9ec5781ef0273ef11",
"sha256:ee9b1cae9a6c5d023e5a150f6f6b9dbb3c3bbc7887d6ee07d4c0ecb49a473734",
"sha256:f1650ea41c408755da5eed52ac6ccbc8938ccc3e698d81e6f6a1be02ff2a0945",
"sha256:f2c0957b3e8c66c10d27272709a5299ab3670a0f187c9428f3b90d267119aedb",
"sha256:f76109387e1ec8d8e2137c94c437b89fe002f29e0881aae8ae45529bdff92000",
"sha256:f8a728511c977df6f3d8af388fcb157e49f11db4a6637dd60131b8b6e40b0253",
"sha256:fb6c3dc3d65014d2c782f5acf0b3ba14e639c6c33d3ed8932ead76b9080b3544"
],
"markers": "python_version >= '3.7'",
"version": "==6.0.3"
},
"mypy-extensions": {
"hashes": [
"sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d",
"sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"
],
"version": "==0.4.3"
},
"pathspec": {
"hashes": [
"sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6",
"sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6"
],
"markers": "python_version >= '3.7'",
"version": "==0.10.3"
},
"peewee": {
"hashes": [
"sha256:2581520c8dfbacd9d580c2719ae259f0637a9e46eda47dfc0ce01864c6366205"
],
"index": "pypi",
"version": "==3.15.4"
},
"platformdirs": {
"hashes": [
"sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca",
"sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e"
],
"markers": "python_version >= '3.7'",
"version": "==2.6.0"
},
"pylint": {
"hashes": [
"sha256:ea82cd6a1e11062dc86d555d07c021b0fb65afe39becbe6fe692efd6c4a67443",
"sha256:ec4a87c33da054ab86a6c79afa6771dc8765cb5631620053e727fcf3ef8cbed7"
],
"index": "pypi",
"version": "==2.15.8"
},
"tomli": {
"hashes": [
"sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
"sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"
],
"markers": "python_full_version < '3.11.0a7'",
"version": "==2.0.1"
},
"tomlkit": {
"hashes": [
"sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b",
"sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73"
],
"markers": "python_full_version >= '3.6.0'",
"version": "==0.11.6"
},
"wrapt": {
"hashes": [
"sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3",
"sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b",
"sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4",
"sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2",
"sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656",
"sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3",
"sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff",
"sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310",
"sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a",
"sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57",
"sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069",
"sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383",
"sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe",
"sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87",
"sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d",
"sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b",
"sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907",
"sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f",
"sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0",
"sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28",
"sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1",
"sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853",
"sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc",
"sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3",
"sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3",
"sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164",
"sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1",
"sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c",
"sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1",
"sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7",
"sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1",
"sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320",
"sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed",
"sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1",
"sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248",
"sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c",
"sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456",
"sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77",
"sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef",
"sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1",
"sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7",
"sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86",
"sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4",
"sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d",
"sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d",
"sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8",
"sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5",
"sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471",
"sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00",
"sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68",
"sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3",
"sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d",
"sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735",
"sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d",
"sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569",
"sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7",
"sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59",
"sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5",
"sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb",
"sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b",
"sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f",
"sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462",
"sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015",
"sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"
],
"markers": "python_version < '3.11'",
"version": "==1.14.1"
},
"yarl": {
"hashes": [
"sha256:009a028127e0a1755c38b03244c0bea9d5565630db9c4cf9572496e947137a87",
"sha256:0414fd91ce0b763d4eadb4456795b307a71524dbacd015c657bb2a39db2eab89",
"sha256:0978f29222e649c351b173da2b9b4665ad1feb8d1daa9d971eb90df08702668a",
"sha256:0ef8fb25e52663a1c85d608f6dd72e19bd390e2ecaf29c17fb08f730226e3a08",
"sha256:10b08293cda921157f1e7c2790999d903b3fd28cd5c208cf8826b3b508026996",
"sha256:1684a9bd9077e922300ecd48003ddae7a7474e0412bea38d4631443a91d61077",
"sha256:1b372aad2b5f81db66ee7ec085cbad72c4da660d994e8e590c997e9b01e44901",
"sha256:1e21fb44e1eff06dd6ef971d4bdc611807d6bd3691223d9c01a18cec3677939e",
"sha256:2305517e332a862ef75be8fad3606ea10108662bc6fe08509d5ca99503ac2aee",
"sha256:24ad1d10c9db1953291f56b5fe76203977f1ed05f82d09ec97acb623a7976574",
"sha256:272b4f1599f1b621bf2aabe4e5b54f39a933971f4e7c9aa311d6d7dc06965165",
"sha256:2a1fca9588f360036242f379bfea2b8b44cae2721859b1c56d033adfd5893634",
"sha256:2b4fa2606adf392051d990c3b3877d768771adc3faf2e117b9de7eb977741229",
"sha256:3150078118f62371375e1e69b13b48288e44f6691c1069340081c3fd12c94d5b",
"sha256:326dd1d3caf910cd26a26ccbfb84c03b608ba32499b5d6eeb09252c920bcbe4f",
"sha256:34c09b43bd538bf6c4b891ecce94b6fa4f1f10663a8d4ca589a079a5018f6ed7",
"sha256:388a45dc77198b2460eac0aca1efd6a7c09e976ee768b0d5109173e521a19daf",
"sha256:3adeef150d528ded2a8e734ebf9ae2e658f4c49bf413f5f157a470e17a4a2e89",
"sha256:3edac5d74bb3209c418805bda77f973117836e1de7c000e9755e572c1f7850d0",
"sha256:3f6b4aca43b602ba0f1459de647af954769919c4714706be36af670a5f44c9c1",
"sha256:3fc056e35fa6fba63248d93ff6e672c096f95f7836938241ebc8260e062832fe",
"sha256:418857f837347e8aaef682679f41e36c24250097f9e2f315d39bae3a99a34cbf",
"sha256:42430ff511571940d51e75cf42f1e4dbdded477e71c1b7a17f4da76c1da8ea76",
"sha256:44ceac0450e648de86da8e42674f9b7077d763ea80c8ceb9d1c3e41f0f0a9951",
"sha256:47d49ac96156f0928f002e2424299b2c91d9db73e08c4cd6742923a086f1c863",
"sha256:48dd18adcf98ea9cd721a25313aef49d70d413a999d7d89df44f469edfb38a06",
"sha256:49d43402c6e3013ad0978602bf6bf5328535c48d192304b91b97a3c6790b1562",
"sha256:4d04acba75c72e6eb90745447d69f84e6c9056390f7a9724605ca9c56b4afcc6",
"sha256:57a7c87927a468e5a1dc60c17caf9597161d66457a34273ab1760219953f7f4c",
"sha256:58a3c13d1c3005dbbac5c9f0d3210b60220a65a999b1833aa46bd6677c69b08e",
"sha256:5df5e3d04101c1e5c3b1d69710b0574171cc02fddc4b23d1b2813e75f35a30b1",
"sha256:63243b21c6e28ec2375f932a10ce7eda65139b5b854c0f6b82ed945ba526bff3",
"sha256:64dd68a92cab699a233641f5929a40f02a4ede8c009068ca8aa1fe87b8c20ae3",
"sha256:6604711362f2dbf7160df21c416f81fac0de6dbcf0b5445a2ef25478ecc4c778",
"sha256:6c4fcfa71e2c6a3cb568cf81aadc12768b9995323186a10827beccf5fa23d4f8",
"sha256:6d88056a04860a98341a0cf53e950e3ac9f4e51d1b6f61a53b0609df342cc8b2",
"sha256:705227dccbe96ab02c7cb2c43e1228e2826e7ead880bb19ec94ef279e9555b5b",
"sha256:728be34f70a190566d20aa13dc1f01dc44b6aa74580e10a3fb159691bc76909d",
"sha256:74dece2bfc60f0f70907c34b857ee98f2c6dd0f75185db133770cd67300d505f",
"sha256:75c16b2a900b3536dfc7014905a128a2bea8fb01f9ee26d2d7d8db0a08e7cb2c",
"sha256:77e913b846a6b9c5f767b14dc1e759e5aff05502fe73079f6f4176359d832581",
"sha256:7a66c506ec67eb3159eea5096acd05f5e788ceec7b96087d30c7d2865a243918",
"sha256:8c46d3d89902c393a1d1e243ac847e0442d0196bbd81aecc94fcebbc2fd5857c",
"sha256:93202666046d9edadfe9f2e7bf5e0782ea0d497b6d63da322e541665d65a044e",
"sha256:97209cc91189b48e7cfe777237c04af8e7cc51eb369004e061809bcdf4e55220",
"sha256:a48f4f7fea9a51098b02209d90297ac324241bf37ff6be6d2b0149ab2bd51b37",
"sha256:a783cd344113cb88c5ff7ca32f1f16532a6f2142185147822187913eb989f739",
"sha256:ae0eec05ab49e91a78700761777f284c2df119376e391db42c38ab46fd662b77",
"sha256:ae4d7ff1049f36accde9e1ef7301912a751e5bae0a9d142459646114c70ecba6",
"sha256:b05df9ea7496df11b710081bd90ecc3a3db6adb4fee36f6a411e7bc91a18aa42",
"sha256:baf211dcad448a87a0d9047dc8282d7de59473ade7d7fdf22150b1d23859f946",
"sha256:bb81f753c815f6b8e2ddd2eef3c855cf7da193b82396ac013c661aaa6cc6b0a5",
"sha256:bcd7bb1e5c45274af9a1dd7494d3c52b2be5e6bd8d7e49c612705fd45420b12d",
"sha256:bf071f797aec5b96abfc735ab97da9fd8f8768b43ce2abd85356a3127909d146",
"sha256:c15163b6125db87c8f53c98baa5e785782078fbd2dbeaa04c6141935eb6dab7a",
"sha256:cb6d48d80a41f68de41212f3dfd1a9d9898d7841c8f7ce6696cf2fd9cb57ef83",
"sha256:ceff9722e0df2e0a9e8a79c610842004fa54e5b309fe6d218e47cd52f791d7ef",
"sha256:cfa2bbca929aa742b5084fd4663dd4b87c191c844326fcb21c3afd2d11497f80",
"sha256:d617c241c8c3ad5c4e78a08429fa49e4b04bedfc507b34b4d8dceb83b4af3588",
"sha256:d881d152ae0007809c2c02e22aa534e702f12071e6b285e90945aa3c376463c5",
"sha256:da65c3f263729e47351261351b8679c6429151ef9649bba08ef2528ff2c423b2",
"sha256:de986979bbd87272fe557e0a8fcb66fd40ae2ddfe28a8b1ce4eae22681728fef",
"sha256:df60a94d332158b444301c7f569659c926168e4d4aad2cfbf4bce0e8fb8be826",
"sha256:dfef7350ee369197106805e193d420b75467b6cceac646ea5ed3049fcc950a05",
"sha256:e59399dda559688461762800d7fb34d9e8a6a7444fd76ec33220a926c8be1516",
"sha256:e6f3515aafe0209dd17fb9bdd3b4e892963370b3de781f53e1746a521fb39fc0",
"sha256:e7fd20d6576c10306dea2d6a5765f46f0ac5d6f53436217913e952d19237efc4",
"sha256:ebb78745273e51b9832ef90c0898501006670d6e059f2cdb0e999494eb1450c2",
"sha256:efff27bd8cbe1f9bd127e7894942ccc20c857aa8b5a0327874f30201e5ce83d0",
"sha256:f37db05c6051eff17bc832914fe46869f8849de5b92dc4a3466cd63095d23dfd",
"sha256:f8ca8ad414c85bbc50f49c0a106f951613dfa5f948ab69c10ce9b128d368baf8",
"sha256:fb742dcdd5eec9f26b61224c23baea46c9055cf16f62475e11b9b15dfd5c117b",
"sha256:fc77086ce244453e074e445104f0ecb27530d6fd3a46698e33f6c38951d5a0f1",
"sha256:ff205b58dc2929191f68162633d5e10e8044398d7a45265f90a0f1d51f85f72c"
],
"markers": "python_version >= '3.7'",
"version": "==1.8.2"
}
},
"develop": {}
}

84
database/models.py Normal file
View File

@@ -0,0 +1,84 @@
#!/usr/bin/env python3
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
# pylint: disable=too-few-public-methods
"""
An implementation of a SQLite database
"""
import os
import uuid
import datetime
from peewee import (
SqliteDatabase,
Model,
UUIDField,
IntegerField,
CharField,
DateTimeField,
ForeignKeyField,
)
# User can provide path to database, or it will be put next to main.py
DATABASE = os.environ.get("database_path", os.getcwd() + "/baseball.db")
database = SqliteDatabase(DATABASE, pragmas={"foreign_keys": 1})
class BaseModel(Model):
"""All of our models will inherit this class
and use the same database connection"""
class Meta:
"""meta"""
database = database
class PlayerModel(BaseModel):
"""Need to keep track of total player score"""
player_id = IntegerField(primary_key=True)
player_name = CharField()
total_points = IntegerField(default=0)
date_joined = DateTimeField(default=datetime.datetime.now)
last_update = DateTimeField(null=True)
def save(self, *args, **kwargs):
"""Should set the last update everytime the record is saved"""
self.last_update = datetime.datetime.now()
super().save(*args, **kwargs)
class GameModel(BaseModel):
"""Games that are ran"""
game_id = UUIDField(primary_key=True, default=uuid.uuid4)
server_id = IntegerField()
pitch_value = IntegerField(null=True)
date_created = DateTimeField(default=datetime.datetime.now)
date_ended = DateTimeField(null=True)
class GuessModel(BaseModel):
"""Guesses for a particular game"""
guess_id = UUIDField(primary_key=True, default=uuid.uuid4)
player = ForeignKeyField(PlayerModel, backref="guesses")
game = ForeignKeyField(GameModel, backref="guesses")
guess = IntegerField(default=0)
difference = IntegerField(null=True)
date_guessed = DateTimeField(null=True)
def create_models():
"""Create database tables"""
with database:
database.create_tables([GameModel, GuessModel, PlayerModel])

View File

@@ -2,26 +2,44 @@
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
# pylint: disable=wrong-import-position
"""
A discord bot that hosts Baseball/Braveball.
A discord game where players guess the pitch speed
from a fantasy baseball pitcher, and whoever is
closer gets more points
"""
import sys
import discord
# Import game functions
sys.path.append('..')
import game
sys.path.append("..")
from game.manager import GameManager
class GhostBallClient(discord.Client):
class BaseBallClient(discord.Client):
"""
Implementation of a Discord client that will monitor
a channel for messages, and if it recieves a message
defined in the Game object, and pass it along
"""
def __init__(self, *args, **kwargs):
super(GhostBallClient, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.game = game.Game()
self.game.discord = self
with GameManager() as self.game:
self.game.discord = self
async def on_ready(self):
"""Method called when connected to Discord"""
print("Logged on as", self.user)
async def on_message(self, message):
"""Method called when a message is recieved"""
# Don't respond to ourself
if message.author == self.user:
return
@@ -31,11 +49,11 @@ class GhostBallClient(discord.Client):
await message.channel.send("pong")
# Game commands
if message.content.startswith('!'):
if message.content.startswith("!"):
firstword = message.content[1:].split()[0]
# Determine if the first word is a command, and run it
for command, function in self.game.commands:
if firstword == command:
self.game.message = message
await function(self.game)
await function()

55
game/base.py Normal file
View File

@@ -0,0 +1,55 @@
#!/usr/bin/env python3
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
# pylint: disable=missing-module-docstring
import logging
from database.models import database, GameModel as Game
class BaseGameManager:
"""Base Game Manager for each Game Manager class to inherit"""
def __init__(self):
# Only one game should run at at time
self.is_running = False
self.commands = []
self.game = Game
# Discord message
self.message = None
# Discord client instance
self.discord = None
logger = logging.getLogger()
console = logging.StreamHandler()
format_str = '%(asctime)s\t%(levelname)s -- %(processName)s %(filename)s:%(lineno)s -- %(message)s'
console.setFormatter(logging.Formatter(format_str))
logger.addHandler(console)
logger.setLevel(logging.DEBUG)
self.logger = logger
def __enter__(self):
"""
Allows use of `with Game() as game` for try/except statements
(https://peps.python.org/pep-0343/)
"""
database.connect()
return self
def __exit__(self, exception_type, exception_value, exception_traceback):
"""
Automagically close the database
when this class has ended execution
"""
database.close()

30
game/clear.py Normal file
View File

@@ -0,0 +1,30 @@
#!/usr/bin/env python3
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
# pylint: disable=missing-module-docstring,too-few-public-methods
from database.models import PlayerModel as Player
from game.base import BaseGameManager
class ClearManager(BaseGameManager):
"""Commands that run when a player clears the session leaderboard"""
def __init__(self):
super().__init__()
self.commands.append(("clear", self.clear))
async def clear(self):
"""Clear command - Clears the session scoreboard"""
players = Player.select(Player.player_id, Player.total_points)
for player in players:
player.total_points = 0
Player.bulk_update(players, fields=[Player.total_points])
clear_message = "The score has been cleared!"
await self.message.channel.send(clear_message)

89
game/end_game.py Normal file
View File

@@ -0,0 +1,89 @@
#!/usr/bin/env python3
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
# pylint: disable=missing-module-docstring
import datetime
from database.models import GameModel as Game, GuessModel as Guess
from game.base import BaseGameManager
from game.process_guess import ProcessGuess
class EndGameManager(BaseGameManager):
"""Commands that run at the end of a play"""
def __init__(self):
super().__init__()
self.commands.append(("resolve", self.stop))
async def update_pitch_value(self):
"""Update game state database for closing arguments"""
pitch_value = self.message.content.split()[1]
if not pitch_value:
return await self.message.channel.send(
f"Invalid command <@{ str(self.message.author.id) }>!"
)
# Save the pitch value
Game.update(
{
Game.pitch_value: pitch_value,
Game.date_ended: datetime.datetime.now(),
}
).where(Game.game_id == self.game.game_id).execute()
return int(pitch_value)
async def stop(self):
"""
Stop command - Stops the game if it is currently running,
saves the pitch value, and displays differences
"""
if not self.is_running:
return await self.message.channel.send("There is no game running")
# How many valid guesses got placed?
guess_count = (
Guess.select()
.join(Game)
.where((Guess.game.game_id == self.game.game_id) & (Guess.guess > 0))
.count()
)
# Discard the game if there weren't enough players
if guess_count < 2:
self.game = None
self.is_running = False
return await self.message.channel.send(
("Play closed!\n" + "However, there were not enough participants.")
)
message = (
"Closed this play! Here are the results\n"
+ "__PLAYER | GUESS | DIFFERENCE | POINTS GAINED | TOTAL POINTS__\n"
)
pitch_value = await self.update_pitch_value()
guess_processor = ProcessGuess(
game=self, pitch_value=pitch_value, message=message
)
(
message,
closest_player_id,
furthest_player_id,
) = guess_processor.process_guesses()
message += (
f"\nCongrats <@{closest_player_id}>! You were the closest!\n"
+ f"Sorry <@{furthest_player_id}>, you were way off"
)
await self.message.channel.send(message)
# stop and discard game
self.is_running = False
self.game = None

52
game/guess.py Normal file
View File

@@ -0,0 +1,52 @@
#!/usr/bin/env python3
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
# pylint: disable=missing-module-docstring,too-few-public-methods
from database.models import PlayerModel as Player, GuessModel as Guess
from game.base import BaseGameManager
class GuessManager(BaseGameManager):
"""Commands that run when a player makes a guess"""
def __init__(self):
super().__init__()
self.commands.append(("guess", self.guess))
async def guess(self):
"""
Guess command - Allows the player to add a guess to the current
running game
"""
if not self.is_running:
return await self.message.channel.send("There is no game running")
value = int(self.message.content.split()[1])
if value < 1 or value > 1000:
return await self.message.channel.send(
"Invalid value. It must be between 1 and 1000 inclusive"
)
# Create player if they don't exist
player, _ = Player.get_or_create(
player_id=self.message.author.id, player_name=self.message.author.name
)
# Create the guess (or allow us to say update successful)
_, created = Guess.get_or_create(
game_id=self.game.game_id, player_id=player.player_id
)
Guess.update({"guess": value}).where(
(Guess.game == self.game.game_id) & (Guess.player == self.message.author.id)
).execute()
if created:
return await self.message.add_reaction("\N{THUMBS UP SIGN}")
return await self.message.channel.send(
f"<@{ str(self.message.author.id) }> your guess has been updated"
)

34
game/help.py Normal file
View File

@@ -0,0 +1,34 @@
#!/usr/bin/env python3
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
# pylint: disable=missing-module-docstring,too-few-public-methods
from game.base import BaseGameManager
class HelpManager(BaseGameManager):
"""Commands that run when a player asks for help"""
def __init__(self):
super().__init__()
self.commands.append(("help", self.help))
async def help(self):
"""help command - Sends a DM to the requesting user with available commands"""
help_message = (
"Braveball commands\n"
+ "ping - Will respond 'pong' if the bot is alive\n"
+ "!braveball - Start new game\n"
+ "!guess - While a game is running, add a guess"
+ " (or update an existing one) from 1-1000\n"
+ "!resolve <value> - 1-1000 to resolve the game\n"
+ "!clear - Clear the session scoreboard\n"
+ "!points - Shows a table of the most recent players, and their scores\n"
+ "!reset - Removes all players and total points\n"
+ "!help - Shows this message"
)
recipient = await self.discord.fetch_user(self.message.author.id)
await recipient.send(help_message)

27
game/manager.py Normal file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env python3
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
# pylint: disable=no-member
"""
A Context Manager / State Machine that keeps track of
a single game instance (there should only be one) in a
Discord channel
"""
from game.new_game import NewGameManager
from game.end_game import EndGameManager
from game.guess import GuessManager
from game.points import PointsManager
from game.reset import ResetManager
from game.help import HelpManager
from game.clear import ClearManager
class GameManager(
NewGameManager, EndGameManager, GuessManager, PointsManager, ResetManager, HelpManager, ClearManager
):
"""
Represents what this bot is able to do on a channel (or DMs)
"""

31
game/new_game.py Normal file
View File

@@ -0,0 +1,31 @@
#!/usr/bin/env python3
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
# pylint: disable=missing-module-docstring,too-few-public-methods
from database.models import GameModel as Game
from game.base import BaseGameManager
class NewGameManager(BaseGameManager):
"""Commands that run at the start of a new game"""
def __init__(self):
super().__init__()
self.commands.append(("bb", self.start))
async def start(self):
"""
Start command - Starts a new game if there isn't already one running
"""
if self.is_running:
return await self.message.channel.send("A game is already running")
self.is_running = True
# game.pitch_value is unknown at the start of the game
self.game = Game.create(server_id=self.message.channel.id)
await self.message.channel.send("Send me your guesses with !guess <number>")

44
game/points.py Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env python3
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
# pylint: disable=not-an-iterable,missing-module-docstring,too-few-public-methods
from database.models import PlayerModel as Player
from game.base import BaseGameManager
class PointsManager(BaseGameManager):
"""Commands that run when a player wants to view the session leaderboard"""
def __init__(self):
super().__init__()
self.commands.append(("points", self.points))
async def points(self):
"""
Points command - returns a table ordered from highest to lowest
of the players and their points
"""
message = (
"\nPlayers, who played recently, with their points highest to lowest\n\n"
)
message += "Player | Total Points | Last Played\n"
players = Player.select(
Player.player_name, Player.total_points, Player.last_update
).order_by(Player.total_points.desc())
for player in players:
message += (
" | ".join(
[
player.player_name,
str(player.total_points),
str(player.last_update)[:-10],
]
)
+ "\n"
)
return await self.message.channel.send(message)

182
game/process_guess.py Normal file
View File

@@ -0,0 +1,182 @@
#!/usr/bin/env python3
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
# pylint: disable=unnecessary-lambda,line-too-long,missing-module-docstring
import math
from database.models import (
GuessModel as Guess,
PlayerModel as Player,
)
class ProcessGuess:
"""
A helper class for the GameManager that handles the
logic for all of the players at the end of a game
"""
def __init__(self, game, pitch_value, message):
self.game_manager = game
self.message = message
self.pitch_value = pitch_value
self.difference = 0
self.difference_score = 0
self.guesses = [Guess]
self.guess = Guess
def get_guesses(self):
"""
Get all guesses for this game as a list of combo Guess + Player models,
excluding invalid results, from lowest to highest value
http://docs.peewee-orm.com/en/latest/peewee/query_examples.html#joins-and-subqueries
"""
self.guesses = (
Guess.select(
Guess.guess,
Player.player_id,
Player.player_name,
Player.total_points,
Guess.difference
)
.join(Player)
.where(
(Guess.game == self.game_manager.game.game_id)
& (Guess.guess > 0)
& (Guess.player.player_id == Player.player_id)
)
.order_by(Guess.difference)
)
return self.guesses
def update_difference_value(self):
"""Store the difference between the player's guessed value and the pitch_value"""
Guess.update({"difference": self.difference}).where(
(Guess.game == self.game_manager.game.game_id)
& (Guess.player == self.guess.player.player_id)
& (Guess.guess_id == self.guess.guess_id)
).execute()
def update_player_total_points(self):
"""Update player's total score with how many points they won this round"""
Player.update(
{
"total_points": math.floor(
self.guess.player.total_points + self.difference_score
)
}
).where(Player.player_id == self.guess.player.player_id).execute()
def get_difference(self, guess=None):
"""Difference calculation, includes "loop over" effect"""
self.game_manager.logger.debug("get_difference")
self.game_manager.logger.debug(guess if guess else "")
if not guess:
guess = self.guess.guess
self.game_manager.logger.debug(f"guess: {guess}")
difference = abs(guess - self.pitch_value)
self.game_manager.logger.debug(f"Difference:{difference}")
if difference > 500:
difference = 1000 - difference
self.game_manager.logger.debug("Diff loop over 500")
self.game_manager.logger.debug(f"{difference}")
self.difference = difference
return self.difference
def get_difference_score(self):
"""
Calculate points for the player based on how close
they are (within range of 0-500) to the pitch_value
"""
self.game_manager.logger.debug("> get_difference_score")
self.game_manager.logger.debug(self.difference)
if self.difference == 0:
self.difference_score = 15
self.game_manager.logger.debug("0 Diff")
elif self.difference > 0 and self.difference < 21:
self.difference_score = 8
self.game_manager.logger.debug("0 to 20 diff")
elif self.difference > 20 and self.difference < 51:
self.difference_score = 5
self.game_manager.logger.debug("21 to 50 Diff")
elif self.difference > 50 and self.difference < 101:
self.difference_score = 3
self.game_manager.logger.debug("51 to 100 Diff")
elif self.difference > 100 and self.difference < 151:
self.difference_score = 2
self.game_manager.logger.debug("101 to 150 Diff")
elif self.difference > 150 and self.difference < 201:
self.difference_score = 1
self.game_manager.logger.debug("151 to 200 Diff")
elif self.difference > 200 and self.difference < 495:
self.difference_score = 0
self.game_manager.logger.debug("Diff too big")
else:
self.difference_score = -5
self.game_manager.logger.debug("Big succ")
return self.difference_score
def get_winner_loser(self):
"""Determine which guesses are closest and furthest from the pitch_value"""
self.game_manager.logger.debug("> get_winner_loser")
guess_values = [record.guess for record in self.get_guesses()]
self.game_manager.logger.debug(", ".join([str(guess) for guess in guess_values]))
# Closest to the pitch_value
winner = min(guess_values, key=lambda guess: self.get_difference(guess))
self.game_manager.logger.debug(f"winner: {winner}")
# Furthest from the pitch_value
loser = max(guess_values, key=lambda guess: self.get_difference(guess))
self.game_manager.logger.debug(f"loser: {loser}")
return winner, loser
def process_guesses(self):
"""
Iterates through the guesses for this game, and appends to the message string
the results of how well that player performed this round.
Uses the pitch_value to determine the difference from their guess to the correct score
"""
winner, loser = self.get_winner_loser()
for guess in self.get_guesses():
self.guess = guess
self.game_manager.logger.debug(f"Current guess: {guess}")
difference = self.get_difference()
difference_score = self.get_difference_score()
self.update_difference_value()
self.update_player_total_points()
self.message += f"{guess.player.player_name} | {guess.guess} | {difference} | {difference_score} | {(guess.player.total_points + difference_score)}\n"
self.game_manager.logger.debug(f"new total: {(guess.player.total_points + difference_score)}")
if guess.guess == winner:
closest_player_id = guess.player.player_id
if guess.guess == loser:
furthest_player_id = guess.player.player_id
self.game_manager.logger.debug(self.message)
return self.message, closest_player_id, furthest_player_id

22
game/reset.py Normal file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python3
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
# pylint: disable=missing-module-docstring,too-few-public-methods
from database.models import PlayerModel as Player
from game.base import BaseGameManager
class ResetManager(BaseGameManager):
"""Commands that run when a player asks for help"""
def __init__(self):
super().__init__()
self.commands.append(("reset", self.reset))
async def reset(self):
"""Reset command purges all players (removes total points)"""
Player.delete().where(True).execute()
return await self.message.channel.send("ok")

4
lint.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
pylint main.py game discord_client database > lint.log
black main.py game discord_client database

View File

@@ -2,18 +2,20 @@
# Copyright 2022 - c0de <c0de@c0de.dev>
# Licensed under the MIT License (https://opensource.org/licenses/MIT)
# pylint: disable=missing-module-docstring
import os
from pathlib import Path
from .discord_client import client
from .database.models import DATABASE, database, create_models
if __name__ == '__main__':
client = GhostBallClient()
client.run(os.environ.get('discord_token'))
from discord import Intents
from discord_client.client import BaseBallClient
from database.models import DATABASE, database, create_models
if __name__ == "__main__":
# Set up the database if we haven't already
if not os.path.exists(DATABASE):
database.connect()
create_models()
database.close()
client = BaseBallClient(intents=Intents.all())
client.run(os.environ.get("discord_token"))

View File

@@ -1,34 +1,85 @@
# Discord Ghost Ball Bot
# Discord Baseball Bot
A bot that will listen on a discord channel, accpeting commands.
The main commands are `!start`, `!guess [int]` and `!resolve [int]`.
The integer will be between 1 and 1000, and the person with the closest guess will win points.
The point scale is:
* 0-25 - 100 pts
* 26-50 - 75 pts
* 51-75 - 50 pts
* 76-100 - 25 pts
There should also be a running total for each player that can be checked with a command.
The main commands are `!braveball`, `!guess [int]` and `!resolve [int]`
Use the `!help` command for more information
## Requirements
You will need a discord bot already set up and supply an auth token.
1. You will need a discord bot and an auth token
1. You will need access to a server that runs Docker
* For development, docker is optional. See Development section of Usage below
Python requirements can be installed with `pipenv install` (install pipenv with `pip3 install pipenv`)
## Points
Points are determined by the difference, which comes from this formula:
```python
difference = abs(guess - pitch_value)
if difference > 500:
difference = 1000 - difference
```
The point scale is a range based on the difference
| minimum | maximum | points |
|-----------------|-----------------|--------|
| (no difference) | (no difference) | 15 |
| 1 | 20 | 8 |
| 21 | 50 | 5 |
| 51 | 100 | 3 |
| 101 | 150 | 2 |
| 151 | 200 | 1 |
| 200 | 494 | 0 |
| 495 | 500 | -5 |
Points are added to a running total for each player at the end of each round.
## Usage
All of the new bot code is in the [GhostBallBot](./GhostBallBot/) folder. This documentation is for the new code.
If you are using docker:
The original code is in [src](./src/). Feel free to try to get this working.
1. You need to determine which version of the bot to use
* If you are using a raspberry pi, you want `archarm64`
* Otherwise, you most likely want `archx64`
### Production with docker
This docker command is the minimum required to run the bot:
`docker run -d -e discord_token="<discord token> c0defox/baseballbot:<version>"`
_Note: The above will not persist the database through restarts_
If you want to keep the database, use the following command:
`docker run -d -v database:/database -e database_path="/database/baseball.db" -e discord_token="<discord token>" c0defox/baseballbot:<version>`
### Development with docker
1. Modify the source code
1. Run `docker build -t baseballbot:<tag> .`
1. Run the same docker commands you would in production
_note: You will probably want to purge your builds after a while, as they will eventually take up space_
### Development without docker
Python requirements can be installed with `pipenv install` (install pipenv with `pip3 install pipenv`)
1. Install the python requirements
1. Add discord token to run.sh
1. Execute run.sh
1. Bot should then connect to discord and start responding to commands defined in game.py
1. Modify the source code
1. Start run.sh in the terminal (restart as you make code changes)
If you modify the source code, you should also run `lint.sh`. This will warn you of linting problems that you can choose to fix, as well as format all of the code to the same standard automatically (usually this will resolve linting warnings)
## Roadmap
Things that would be nice to add:
* (yaml) Config file for tweaking various internals
* Admin commands: table clear, record deletion, record update
* Audit log of admin commands, and previous values for anything changed

View File

@@ -1,4 +1,3 @@
export discord_token=''
export database_path='/tmp/ghostball.db'
python3 main.py

View File

@@ -1,51 +0,0 @@
# Names of Configurations
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
DATABASE_USERNAME = 'ghost_user'
DATABASE_PASSWORD = 'root'
DATABASE_HOST = '192.168.0.11'
DATABASE_PORT = '5432'
DATABASE_NAME = 'ghostball'
SEASON_1_SPREADSHEET_ID = 's1_spreadsheet_id'
SEASON_2_SPREADSHEET_ID = 's2_spreadsheet_id'
PLAYER_SPREADSHEET = 'player_spreadsheet'
'''
Main source for configurations fetched from a startup configuration file. Includes the ability to fetch all, or fetch
one configuration once the file is loaded.
You'll find the names of these configs above as constants that can be used throughout the rest of this repository
'''
class Configs():
configs = {}
def __init__(self, config_file_path):
self.config_file_path = config_file_path
self.__load_configs__()
'''
Fetches a single configuration by the name of that configuration.
Returns None if that configuration does not exist
'''
def get_config_by_name(self, name):
try:
return Configs.configs[name]
except KeyError:
return None
'''
Fetches all configurations and returns them as a dictionary of config_key -> config_value
'''
def get_all_configs(self):
return Configs.configs
'''
Performs the initial load of configurations from a startup configuration file
'''
def __load_configs__(self):
Configs.configs = {}
config_file = open(self.config_file_path, 'r')
for line in config_file:
split_line = line.split('=')
Configs.configs[split_line[0]] = split_line[1].strip('\n')

View File

@@ -1,28 +0,0 @@
from sqlalchemy import Column, String, Integer, ForeignKey, Date
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
Base = declarative_base()
class Play(Base):
__tablename__ = 'play'
play_id = Column(String, nullable=False, primary_key=True)
pitch_value = Column(Integer, nullable=True)
creation_date = Column(Date, nullable=False)
server_id = Column(String, nullable=False)
guesses = relationship(lambda : Guess)
class Guess(Base):
__FAKE_VALUE__ = -5000
__tablename__ = 'guess'
member_id = Column(String, nullable=False, primary_key=True)
play_id = Column(UUID, ForeignKey(Play.play_id), nullable=False, primary_key=True)
guessed_number = Column(Integer, nullable=False)
member_name = Column(String, nullable=False)
difference = Column(Integer)
play = relationship("Play", back_populates="guesses")

View File

@@ -1,123 +0,0 @@
from copy import deepcopy
from src.main.database_module.database_classes.db_classes import Guess
from src.main.db_session import DatabaseSession
MEMBER_ID = 'member_id'
PLAY_ID = 'play_id'
GUESSED_NUMBER = 'guessed_number'
DIFFERENCE = 'difference'
MEMBER_NAME = 'member_name'
class GuessDAO():
db_string = None
session = None
Session = None
engine = None
_database_session = None
def __init__(self):
self._database_session = DatabaseSession()
def insert(self, guess_info, allow_update=False):
session = self._database_session.get_or_create_session()
guess = Guess(
member_id=guess_info[MEMBER_ID],
play_id = guess_info[PLAY_ID],
guessed_number = guess_info[GUESSED_NUMBER],
member_name = guess_info[MEMBER_NAME]
)
existing_guess = self.__convert_all__(session\
.query(Guess)\
.filter(Guess.member_id == guess_info[MEMBER_ID], Guess.play_id == guess_info[PLAY_ID]))
if len(existing_guess) == 0:
session.add(guess)
session.commit()
return True
elif allow_update:
session\
.query(Guess)\
.filter(Guess.member_id == guess_info[MEMBER_ID], Guess.play_id == guess_info[PLAY_ID], Guess.member_name == guess_info[MEMBER_NAME])\
.update({Guess.guessed_number: guess_info[GUESSED_NUMBER]})
return True
else:
return False
'''
Converts the database object into a Dictionary, so that the database object is not passed out of the
datastore layer.
'''
def __convert_all__(self, games):
converted_games = []
for game in games:
game_dict = {}
for column in game.__dict__:
game_dict[column] = str(getattr(game, column))
converted_games.append(deepcopy(game_dict))
return converted_games
def set_differences(self, pitch_value, play_id):
session = self._database_session.get_or_create_session()
games_to_update = self.__convert_all__(session.query(Guess).filter(Guess.play_id == play_id))
for game in games_to_update:
difference = self.calculate_difference(pitch_value, game[GUESSED_NUMBER])
session.query(Guess).filter(Guess.member_id == game[MEMBER_ID], Guess.play_id == game[PLAY_ID]).update({Guess.difference: difference})
session.commit()
def calculate_difference(self, pitch_value, guess_value):
pitched_number = int(pitch_value)
possible_value = abs(int(guess_value) - pitched_number)
if possible_value > 500:
return 1000 - possible_value
else:
return possible_value
def fetch_closest(self, num_to_fetch):
session = self._database_session.get_or_create_session()
return self.__convert_all__(
session\
.query(Guess)\
.order_by(Guess.difference)\
.limit(num_to_fetch)
)
def refresh(self):
self._database_session.__create_new_session__() # I know, I know. It's fine.
def get_all_guesses_for_plays(self, play_ids):
session = self._database_session.get_or_create_session()
return self.__convert_all__(
session
.query(Guess)
.filter(Guess.play_id.in_(play_ids))
)
def get_closest_on_play(self, play):
session = self._database_session.get_or_create_session()
# TODO: Make this a MAX query for ties
converted_guesses = self.__convert_all__(
session
.query(Guess)
.filter(Guess.play_id == play)
.order_by(Guess.difference)
.limit(1)
)
if len(converted_guesses) > 1:
raise AssertionError("More than one best guess! Can't continue!")
elif len(converted_guesses) == 0:
return None
else:
return converted_guesses[0]

View File

@@ -1,96 +0,0 @@
from copy import deepcopy
from sqlalchemy.sql.expression import and_
from src.main.db_session import DatabaseSession
from src.main.database_module.database_classes.db_classes import Play
import datetime
PLAY_ID = 'play_id'
PITCH_VALUE = 'pitch_value'
CREATION_DATE = 'creation_date'
SERVER_ID = 'server_id'
class PlayDAO():
db_string = None
session = None
Session = None
engine = None
_database_session = None
def __init__(self):
self._database_session = DatabaseSession()
def insert(self, play_info):
session = self._database_session.get_or_create_session()
play = Play(
play_id = play_info[PLAY_ID],
pitch_value = play_info[PITCH_VALUE] if PITCH_VALUE in play_info else None,
creation_date = play_info[CREATION_DATE],
server_id = play_info[SERVER_ID]
)
session.add(play)
session.commit()
def get_play_by_id(self, input_id):
session = self._database_session.get_or_create_session()
return self.__convert_all__(session.query(Play).filter(Play.play_id == input_id))
def get_all_plays_after(self, timestamp, input_server_id):
session = self._database_session.get_or_create_session()
return self.__convert_all__(session.query(Play).filter(and_(Play.server_id == str(input_server_id), Play.creation_date > timestamp)))
def get_all_plays_on_server(self, input_server_id, earliest_timestamp):
session = self._database_session.get_or_create_session()
converted_datetime = datetime.datetime.fromtimestamp(earliest_timestamp / 1000.0)
return self.__convert_all__(session.query(Play).filter(and_(Play.server_id == str(input_server_id), Play.creation_date > converted_datetime)))
'''
Checks to see if there is a play that is currently active or not
'''
def is_active_play(self, server_id):
return self.get_active_play(server_id) != None
def get_active_play(self, input_server_id):
session = self._database_session.get_or_create_session()
plays = self.__convert_all__(session.query(Play).filter(and_(Play.pitch_value == None, Play.server_id == str(input_server_id))))
if len(plays) > 1:
raise AssertionError("More than one active play! Can't continue!")
elif len(plays) == 0:
return None
else:
return plays[0]
def resolve_play(self, input_pitch, input_server_id):
session = self._database_session.get_or_create_session()
active_id = self.get_active_play(input_server_id)
session\
.query(Play)\
.filter(and_(Play.pitch_value == None, Play.server_id == str(input_server_id)))\
.update({Play.pitch_value: input_pitch})
session.commit()
return active_id
def refresh(self):
self._database_session.__create_new_session__() # I know, I know. It's fine.
'''
Converts the database object into a Dictionary, so that the database object is not passed out of the
datastore layer.
'''
def __convert_all__(self, plays):
converted_plays = []
for play in plays:
play_dict = {}
for column in play.__dict__:
play_dict[column] = str(getattr(play, column))
converted_plays.append(deepcopy(play_dict))
return converted_plays

View File

@@ -1,49 +0,0 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import sys
import sqlite3
sys.path.append('../../../../../../src')
from src.main.configs import Configs, DATABASE_USERNAME, DATABASE_PASSWORD, DATABASE_HOST, DATABASE_NAME
'''
Stores a database session for use throughout the application. Must be initialized at startup before any database calls
are made and AFTER the Configurations are setup.
This shouldn't need to be touched after startup. To use, see the sqlalchemy docs...or just start by calling Session()
and then use it to handle the necessary CRUD operations.
You should NOT instantiate this in any method except the main application runner
'''
class DatabaseSession():
_session = None
def __init__(self):
self.__create_new_session__()
def __create_new_session__(self):
if self._session is not None:
self._session.close()
config_map = Configs.configs
db_string = self._pgsql_conn_string_(config_map)
Session = sessionmaker(create_engine(db_string))
self._session = Session()
return self._session
def get_or_create_session(self):
try:
self._session.connection()
return self._session
except: # The linter can scream all it wants, this makes sense. If it's this broke, we want a new one anyway.
return self.__create_new_session__()
# Look, this kinda sucks. But it's for fun and friends and I'm doing it quick and dirty.
def _pgsql_conn_string_(self, config_map):
return 'postgresql://%s:%s@%s/%s' % \
(config_map[DATABASE_USERNAME], config_map[DATABASE_PASSWORD], config_map[DATABASE_HOST], config_map[DATABASE_NAME])
def _sqlite_conn_string(self, config_map):
return "sqlite:///ghostball.db"

View File

@@ -1,220 +0,0 @@
import sys
import discord
from discord.utils import get
import uuid
import datetime
import dateparser
from src.main.configs import Configs
from src.main.database_module.guess_dao import GuessDAO, GUESSED_NUMBER, MEMBER_ID, MEMBER_NAME, DIFFERENCE
from src.main.services.points_service import PointsService
from src.main.database_module.play_dao import PlayDAO, PLAY_ID, CREATION_DATE, SERVER_ID
from src.main.db_session import DatabaseSession
from src.main.discord_module.leaderboard_config import LeaderboardConfig
play_dao = None
guess_dao = None
points_service = PointsService()
bot = discord.Client()
@bot.event
async def on_ready():
print('Logged in as')
print(bot.user.name)
print(bot.user.id)
print('------')
@bot.event
async def on_message(message):
if message.author == bot.user:
return
content = message.content
server_id = message.guild.id
'''
Sets up the next set of guesses.
'''
if content.startswith('!ghostball'):
if play_dao.is_active_play(server_id):
await message.channel.send("There's already an active play. Could you close that one first, please?")
else:
generated_play_id = uuid.uuid4()
play_object = {PLAY_ID: generated_play_id, CREATION_DATE: datetime.datetime.now(), SERVER_ID: server_id}
play_dao.insert(play_object)
await message.channel.send("@flappy ball, pitch is in! Send me your guesses with a !guess command.")
if content.startswith("!guess"):
guess_value = None
try:
guess_value = __parse_guess__(content)
except ValueError:
await message.channel.send("That number is not between 1 and 1000. We're still in MLN so don't try to cheat.")
return
if guess_value is None:
await message.channel.send("I don't know what you did but I'm pretty sure you're tyring to break the bot so please stop.")
return
if not play_dao.is_active_play(server_id):
await message.channel.send("Hey, there's no active play! Start one up first with !ghostball.")
else:
play = play_dao.get_active_play(server_id)
guess_object = {PLAY_ID: play['play_id'],
MEMBER_ID: str(message.author.id),
GUESSED_NUMBER: guess_value,
MEMBER_NAME: str(message.author.name)}
if guess_dao.insert(guess_object, allow_update=True):
await message.add_reaction(emoji="\N{THUMBS UP SIGN}")
# Closes off the active play to be ready for the next set
if content.startswith('!resolve'):
# try:
pitch_number, batter_id, batter_guess, has_batter = __parse_resolve_play__(content)
if args is None:
await message.channel.send("Hey " + "<@" + str(message.author.id) + ">, I'm not sure what you meant. "
"You need real, numeric, values for this command to work. "
"Use !resolve <pitch number> <optional batter> <optional swing number>"
" and try again.")
# Check if we have an active play
if not play_dao.is_active_play(server_id):
await message.channel.send("You confused me. There's no active play so I have nothing to close!")
else:
if has_batter:
referenced_member_id = batter_id[3:-1]
play = play_dao.get_active_play(server_id)
guess_object = {PLAY_ID: play['play_id'],
MEMBER_ID: str(referenced_member_id),
GUESSED_NUMBER: batter_guess,
MEMBER_NAME: bot.get_user(int(referenced_member_id)).name}
guess_dao.insert(guess_object, True)
play = play_dao.resolve_play(pitch_number, server_id)
guess_dao.set_differences(pitch_number, play['play_id'])
guesses = points_service.fetch_sorted_guesses_by_play(guess_dao, play['play_id'])
response_message = "Closed this play! Here are the results:\n"
response_message += "PLAYER --- DIFFERENCE --- POINTS GAINED\n"
for guess in guesses:
response_message += guess[1] + " --- " + str(guess[2]) + " --- " + str(guess[3]) + "\n"
if len(guesses) < 2:
response_message += "Not enough people participated to give best and worst awards. Stop being lazy."
else:
response_message += "\nCongrats to <@" + str(guesses[0][0]) + "> for being the closest! \n"
response_message += "And tell <@" + str(guesses[-1][0]) + "> they suck."
await message.channel.send(response_message)
if content.startswith("!points"):
try:
timestamp = __parse_points_message__(content)
except:
await message.channel.send("You gave me a timestamp that was so bad, the best date handling library in the"
" world of software couldn't figure out what you meant. That's...impressive. Now"
" fix your shit and try again.")
return
points_by_user = points_service.fetch_points(timestamp, server_id, play_dao, guess_dao)
response = "Here are the top guessers by points as per your request..."
for user in points_by_user:
if str(user[2]) != '0':
response += "\n" + str(user[1]) + " : " + str(user[2])
await message.channel.send(response)
# Refresh Postgres connection
if content.startswith('!restart'):
play_dao.refresh()
guess_dao.refresh()
if content.startswith('!help'):
help_message = __get_help_message__()
recipient = await bot.fetch_user(message.author.id)
await recipient.send(help_message)
def __get_help_message__():
# Start message with person who asked for help
help_message = "Hey! I can be instructed to do any number of things! Use the following commands: \n" \
"!guess <NUMBER> --- This will add your guess to the currently active play. " \
"I will give you a thumbs up if everything worked!\n" \
"!ghostball --- Starts a new play. I'll let you know if this didn't work for some reason!\n" \
"!help --- You just asked for this. If you ask for it again, I'll repeat myself.\n" \
"!resolve <PITCH_NUMBER> <OPTIONAL --- BATTER by @-mention> <OPTIONAL - ACTUAL SWING NUMBER> --- " \
"Uses the pitch number and real swing number to figure out who was closest and ends the active play." \
"If you include the batter and their swing number, they will get credit for how well they did!\n" \
"!points <OPTIONAL --- TIMESTAMP> Fetches all plays since your requested time, or the beginning of the unvierse " \
"if none given. Will currently always dump all players - top X coming soon...\n" \
"!restart --- If the bot looks broken, this will take a shot at fixing it. It won't answer your commands " \
"for about 3 seconds after you do this! BE CAREFUL! ONLY USE IN AN EMERGENCY!\n" \
"<PING KALI IF YOU'RE CONFUSED, ANGRY, OR WANT TO GEEK OUT ABOUT BRAVELY DEFAULT!>\n"
return help_message
def __parse_leaderboard_message__(message_content):
return LeaderboardConfig(message_content)
def __parse_points_message__(message_content):
pieces = message_content.split(' ')
if len(pieces) > 1:
try:
timestamp = dateparser.parse(pieces[1])
except:
raise RuntimeError("Unable to parse timestamp!")
else:
timestamp = dateparser.parse("1970-01-01")
return timestamp
def __parse_guess__(message_content):
pieces = message_content.split(' ')
try:
guess_value = pieces[1]
guess_as_int = int(guess_value)
if guess_as_int > 1000 or guess_as_int < 1:
raise ValueError("Number not between 1 and 1000 inclusive")
else:
return guess_value
except TypeError:
return None
def __parse_resolve_play__(message_content):
pieces = message_content.split()
try:
if len(pieces) == 2:
return pieces[1], None, None, False
elif len(pieces) == 4:
return pieces[1], pieces[2], pieces[3], True
else:
print("Illegal resolution command")
return None, None
except TypeError:
return None, None
if __name__ == '__main__':
args = sys.argv
token = args[1]
file_path = args[2]
configs = Configs(file_path)
databaseSession = DatabaseSession()
play_dao = PlayDAO()
guess_dao = GuessDAO()
bot.run(token)

View File

@@ -1,15 +0,0 @@
class LeaderboardConfig():
closest = True
def __init__(self, message_content):
pieces = message_content.split(' ')
if len(pieces) == 1:
return
if pieces[1] == 'average':
self.closest = False
def should_sort_by_pure_closest(self):
return self.closest
def should_sort_by_best_average(self):
return not self.closest

View File

@@ -1,48 +0,0 @@
class PointsService():
_point_table = [(5,25),(25, 75), (75, 50), (150, 25)]
def fetch_points(self, timestamp, server_id, play_dao, guess_dao):
plays = play_dao.get_all_plays_after(timestamp, server_id)
all_guesses = guess_dao.get_all_guesses_for_plays(x['play_id'] for x in plays)
# Build a dictionary of each member and their total points
totals_by_player = {}
for guess in all_guesses:
if guess['member_id'] in totals_by_player:
totals_by_player[guess['member_id']]['points'] += self.__get_points_for_diff__(guess['difference'])
else:
totals_by_player[guess['member_id']] = {}
totals_by_player[guess['member_id']]['points'] = self.__get_points_for_diff__(guess['difference'])
totals_by_player[guess['member_id']]['member_name'] = guess['member_name']
# And now pull those numbers out into a list and sort them
sorted_players = []
for player in totals_by_player:
sorted_players.append([player,
totals_by_player[player]['member_name'],
totals_by_player[player]['points']])
sorted_players.sort(key=lambda x: x[2], reverse=True)
return sorted_players
def fetch_sorted_guesses_by_play(self, guess_dao, play_id):
all_guesses = guess_dao.get_all_guesses_for_plays([play_id])
player_list = []
for guess in all_guesses:
player_list.append([guess['member_id'], guess['member_name'], int(guess['difference']), self.__get_points_for_diff__(guess['difference'])])
player_list.sort(key=lambda x: x[2])
return player_list
# Iterates through the point table, which we assume is sorted, and gets the points
def __get_points_for_diff__(self, diff):
if diff == 'None':
return 0
for i in range(0, len(self._point_table)):
if int(diff) < self._point_table[i][0]:
return self._point_table[i][1]
return 0

View File