easterobot 1.3.2__tar.gz → 1.5.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {easterobot-1.3.2 → easterobot-1.5.2}/.gitignore +1 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/.vscode/settings.json +1 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/PKG-INFO +23 -19
- {easterobot-1.3.2 → easterobot-1.5.2}/README.rst +22 -18
- {easterobot-1.3.2 → easterobot-1.5.2}/conftest.py +4 -3
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/bot.py +14 -1
- easterobot-1.5.2/easterobot/casino/__init__.py +1 -0
- easterobot-1.5.2/easterobot/casino/roulette.py +269 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/commands/__init__.py +2 -0
- easterobot-1.5.2/easterobot/commands/game.py +204 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/commands/reset.py +11 -14
- easterobot-1.5.2/easterobot/commands/roulette.py +34 -0
- easterobot-1.5.2/easterobot/commands/top.py +102 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/config.py +35 -8
- easterobot-1.3.2/easterobot/games/connect.py → easterobot-1.5.2/easterobot/games/connect4.py +25 -28
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/games/game.py +126 -54
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/games/rock_paper_scissor.py +33 -30
- easterobot-1.5.2/easterobot/games/skyjo.py +805 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/games/tic_tac_toe.py +19 -18
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/hunts/hunt.py +49 -18
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/hunts/rank.py +24 -2
- easterobot-1.5.2/easterobot/info.py +28 -0
- easterobot-1.5.2/easterobot/locker.py +180 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/models.py +9 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/config.example.yml +8 -2
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/credits.txt +2 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/s1.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/s10.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/s11.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/s12.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/s2.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/s3.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/s4.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/s5.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/s6.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/s7.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/s8.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/s9.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sA.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sB.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sC.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sD.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sE.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sF.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sG.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sH.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sI.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sJ.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sK.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sL.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sM.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sN.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sO.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sP.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sQ.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sR.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sS.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sT.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sU.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sV.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sW.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sX.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sY.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/sZ.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/placements/s_.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_back.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_m1.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_m2.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_p0.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_p1.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_p10.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_p11.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_p12.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_p2.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_p3.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_p4.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_p5.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_p6.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_p7.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_p8.png +0 -0
- easterobot-1.5.2/easterobot/resources/emotes/skyjo/skyjo_p9.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/pyproject.toml +2 -1
- easterobot-1.5.2/tools/gen_grid_emoji.py +98 -0
- easterobot-1.5.2/tools/gg sans Bold.ttf +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/uv.lock +91 -1
- easterobot-1.3.2/easterobot/commands/game.py +0 -191
- easterobot-1.3.2/easterobot/commands/top.py +0 -94
- easterobot-1.3.2/easterobot/info.py +0 -18
- {easterobot-1.3.2 → easterobot-1.5.2}/.dockerignore +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/.editorconfig +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/.github/actions/setup-project/action.yml +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/.github/workflows/docs.yml +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/.github/workflows/lint.yml +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/.github/workflows/publish.yml +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/.github/workflows/tests.yml +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/.pre-commit-config.yaml +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/.vscode/extensions.json +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/.vscode/ltex.dictionary.en-US.txt +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/.vscode/ltex.hiddenFalsePositives.en-US.txt +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/Dockerfile +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/LICENSE +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/docker-compose.yml +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/docs/conf.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/docs/index.rst +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/docs/references.rst +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/docs/resources/favicon.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/__init__.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/__main__.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/alembic/env.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/alembic/script.py.mako +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/alembic/versions/2f0d4305e320_init_database.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/alembic/versions/940c3b9c702d_add_lock_on_eggs.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/cli.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/commands/base.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/commands/basket.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/commands/disable.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/commands/edit.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/commands/enable.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/commands/help.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/commands/info.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/commands/search.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/games/__init__.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/hunts/__init__.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/hunts/luck.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/logger.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/py.typed +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/query.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/alembic.ini +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_01.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_02.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_03.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_04.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_05.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_06.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_07.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_08.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_09.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_10.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_11.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_12.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_13.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_14.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_15.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_16.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_17.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_18.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_19.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/eggs/egg_20.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/icons/arrow.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/icons/end.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/icons/versus.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/emotes/icons/wait.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/logging.conf +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/resources/logo.png +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/easterobot/utils.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/entrypoint.sh +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/tests/__init__.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/tests/constants.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/tests/test_cli.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/tests/test_config.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/tests/test_search.py +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/tools/chatgpt.txt +0 -0
- {easterobot-1.3.2 → easterobot-1.5.2}/tools/cropping.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: easterobot
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.5.2
|
4
4
|
Summary: Discord bot for Easter.
|
5
5
|
Project-URL: Homepage, https://github.com/Dashstrom/easterobot
|
6
6
|
Project-URL: Repository, https://github.com/Dashstrom/easterobot
|
@@ -144,24 +144,28 @@ Configuration directory
|
|
144
144
|
|
145
145
|
.. code-block:: text
|
146
146
|
|
147
|
-
data
|
148
|
-
├── .gitignore
|
149
|
-
├── config.yml
|
150
|
-
├── easterobot.db
|
151
|
-
├── logs
|
152
|
-
│ ├── easterobot.log
|
153
|
-
│ └── easterobot.log.1
|
154
|
-
└── resources
|
155
|
-
├── config.example.yml
|
156
|
-
├── credits.txt
|
157
|
-
├── emotes
|
158
|
-
│ ├── eggs
|
159
|
-
│ | └── egg_01.png
|
160
|
-
│
|
161
|
-
│
|
162
|
-
├──
|
163
|
-
|
164
|
-
└──
|
147
|
+
data Root directory
|
148
|
+
├── .gitignore Avoid pushing sensitive data
|
149
|
+
├── config.yml Configuration file
|
150
|
+
├── easterobot.db Database
|
151
|
+
├── logs Logging directory
|
152
|
+
│ ├── easterobot.log Latest log file
|
153
|
+
│ └── easterobot.log.1 Rotating log file
|
154
|
+
└── resources Resource directory
|
155
|
+
├── config.example.yml An example of config
|
156
|
+
├── credits.txt Credits of emotes
|
157
|
+
├── emotes Directory loaded as application emotes
|
158
|
+
│ ├── eggs Directory for eggs
|
159
|
+
│ | └── egg_01.png Emoji to use for egg
|
160
|
+
│ ├── icons Misc emotes to load
|
161
|
+
│ │ └── arrow.png Emoji used in messages
|
162
|
+
│ ├── placements Directory for emoji used in grid
|
163
|
+
│ │ └── s1.png Single blue emoji with one on it
|
164
|
+
│ └── skyjo Skyjo cards
|
165
|
+
│ └── skyjo_m1.png Card with minus -1 with deep blue
|
166
|
+
├── logging.conf Logging configuration
|
167
|
+
├── alembic.ini Configure for alembic
|
168
|
+
└── logo.png Logo used by the bot
|
165
169
|
|
166
170
|
Development
|
167
171
|
###########
|
@@ -106,24 +106,28 @@ Configuration directory
|
|
106
106
|
|
107
107
|
.. code-block:: text
|
108
108
|
|
109
|
-
data
|
110
|
-
├── .gitignore
|
111
|
-
├── config.yml
|
112
|
-
├── easterobot.db
|
113
|
-
├── logs
|
114
|
-
│ ├── easterobot.log
|
115
|
-
│ └── easterobot.log.1
|
116
|
-
└── resources
|
117
|
-
├── config.example.yml
|
118
|
-
├── credits.txt
|
119
|
-
├── emotes
|
120
|
-
│ ├── eggs
|
121
|
-
│ | └── egg_01.png
|
122
|
-
│
|
123
|
-
│
|
124
|
-
├──
|
125
|
-
|
126
|
-
└──
|
109
|
+
data Root directory
|
110
|
+
├── .gitignore Avoid pushing sensitive data
|
111
|
+
├── config.yml Configuration file
|
112
|
+
├── easterobot.db Database
|
113
|
+
├── logs Logging directory
|
114
|
+
│ ├── easterobot.log Latest log file
|
115
|
+
│ └── easterobot.log.1 Rotating log file
|
116
|
+
└── resources Resource directory
|
117
|
+
├── config.example.yml An example of config
|
118
|
+
├── credits.txt Credits of emotes
|
119
|
+
├── emotes Directory loaded as application emotes
|
120
|
+
│ ├── eggs Directory for eggs
|
121
|
+
│ | └── egg_01.png Emoji to use for egg
|
122
|
+
│ ├── icons Misc emotes to load
|
123
|
+
│ │ └── arrow.png Emoji used in messages
|
124
|
+
│ ├── placements Directory for emoji used in grid
|
125
|
+
│ │ └── s1.png Single blue emoji with one on it
|
126
|
+
│ └── skyjo Skyjo cards
|
127
|
+
│ └── skyjo_m1.png Card with minus -1 with deep blue
|
128
|
+
├── logging.conf Logging configuration
|
129
|
+
├── alembic.ini Configure for alembic
|
130
|
+
└── logo.png Logo used by the bot
|
127
131
|
|
128
132
|
Development
|
129
133
|
###########
|
@@ -8,15 +8,16 @@ import pytest
|
|
8
8
|
import pytest_asyncio
|
9
9
|
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
|
10
10
|
|
11
|
-
from easterobot import __author__
|
12
11
|
from easterobot.bot import Easterobot
|
13
12
|
from easterobot.config import MConfig
|
14
13
|
|
15
14
|
|
16
15
|
@pytest.fixture(autouse=True)
|
17
|
-
def
|
16
|
+
def _add_bot(doctest_namespace: dict[str, Any], bot: Easterobot) -> None:
|
18
17
|
"""Update doctest namespace."""
|
19
|
-
doctest_namespace["
|
18
|
+
doctest_namespace["bot"] = bot
|
19
|
+
doctest_namespace["engine"] = bot.engine
|
20
|
+
doctest_namespace["config"] = bot.config
|
20
21
|
|
21
22
|
|
22
23
|
@pytest.fixture
|
@@ -1,5 +1,6 @@
|
|
1
1
|
"""Main program."""
|
2
2
|
|
3
|
+
import asyncio
|
3
4
|
import logging
|
4
5
|
import pathlib
|
5
6
|
import shutil
|
@@ -17,6 +18,7 @@ import discord.app_commands
|
|
17
18
|
import discord.ext.commands
|
18
19
|
from alembic.command import upgrade
|
19
20
|
from sqlalchemy.ext.asyncio import create_async_engine
|
21
|
+
from typing_extensions import override
|
20
22
|
|
21
23
|
if TYPE_CHECKING:
|
22
24
|
from easterobot.games.game import GameCog
|
@@ -44,6 +46,7 @@ class Easterobot(discord.ext.commands.Bot):
|
|
44
46
|
owner: discord.User
|
45
47
|
game: "GameCog"
|
46
48
|
hunt: "HuntCog"
|
49
|
+
init_finished: asyncio.Event
|
47
50
|
|
48
51
|
def __init__(self, config: MConfig) -> None:
|
49
52
|
"""Initialise Easterbot."""
|
@@ -167,6 +170,12 @@ class Easterobot(discord.ext.commands.Bot):
|
|
167
170
|
"""Run the bot with the given token."""
|
168
171
|
self.run(token=self.config.verified_token())
|
169
172
|
|
173
|
+
@override
|
174
|
+
async def start(self, token: str, *, reconnect: bool = True) -> None:
|
175
|
+
"""Add event for starting."""
|
176
|
+
self.init_finished = asyncio.Event()
|
177
|
+
await super().start(token=token, reconnect=reconnect)
|
178
|
+
|
170
179
|
async def on_ready(self) -> None:
|
171
180
|
"""Handle ready event, can be trigger many time if disconnected."""
|
172
181
|
# Sync bot commands
|
@@ -196,6 +205,7 @@ class Easterobot(discord.ext.commands.Bot):
|
|
196
205
|
self.user,
|
197
206
|
getattr(self.user, "id", "unknown"),
|
198
207
|
)
|
208
|
+
self.init_finished.set()
|
199
209
|
|
200
210
|
async def _load_emojis(self) -> None:
|
201
211
|
emojis = {
|
@@ -203,6 +213,8 @@ class Easterobot(discord.ext.commands.Bot):
|
|
203
213
|
for emoji in await self.fetch_application_emojis()
|
204
214
|
}
|
205
215
|
emotes_path = (self.config.resources / "emotes").resolve()
|
216
|
+
# TODO(dashstrom): remove old one !
|
217
|
+
# TODO(dashstrom): cache emoji synced !
|
206
218
|
self.app_emojis = {}
|
207
219
|
for emote in emotes_path.glob("**/*"):
|
208
220
|
if not emote.is_file():
|
@@ -221,4 +233,5 @@ class Easterobot(discord.ext.commands.Bot):
|
|
221
233
|
self.app_emojis[name] = emoji
|
222
234
|
else:
|
223
235
|
logger.info("Load emoji %s", name)
|
224
|
-
|
236
|
+
emoji = emojis[name]
|
237
|
+
self.app_emojis[name] = emoji
|
@@ -0,0 +1 @@
|
|
1
|
+
"""Module for casino events."""
|
@@ -0,0 +1,269 @@
|
|
1
|
+
"""Module to play roulette."""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
from asyncio import sleep
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from typing import TYPE_CHECKING, Union
|
7
|
+
|
8
|
+
import discord
|
9
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
10
|
+
|
11
|
+
from easterobot.bot import Easterobot
|
12
|
+
from easterobot.config import RAND, agree
|
13
|
+
from easterobot.locker import EggLocker
|
14
|
+
from easterobot.utils import in_seconds
|
15
|
+
|
16
|
+
if TYPE_CHECKING:
|
17
|
+
from easterobot.models import Egg
|
18
|
+
|
19
|
+
|
20
|
+
@dataclass(frozen=True, order=True)
|
21
|
+
class Play:
|
22
|
+
name: str
|
23
|
+
emoji: str
|
24
|
+
bet: int
|
25
|
+
payout: int
|
26
|
+
slots: frozenset[int]
|
27
|
+
|
28
|
+
@property
|
29
|
+
def label(self) -> str:
|
30
|
+
"""Returns the label of the bet."""
|
31
|
+
return agree(
|
32
|
+
f"{self.bet} œuf sur {self.name}",
|
33
|
+
f"{self.bet} œufs sur {self.name}",
|
34
|
+
self.bet,
|
35
|
+
)
|
36
|
+
|
37
|
+
@property
|
38
|
+
def probability(self) -> float:
|
39
|
+
"""Returns the winning probability."""
|
40
|
+
return len(self.slots) / 37
|
41
|
+
|
42
|
+
@property
|
43
|
+
def eggs(self) -> float:
|
44
|
+
"""Returns the number of eggs won."""
|
45
|
+
return self.payout * self.bet
|
46
|
+
|
47
|
+
|
48
|
+
# fmt: off
|
49
|
+
plays = [
|
50
|
+
Play("noir", "⚫", 1, 2, frozenset({2, 4, 6, 8, 10, 11, 13, 15, 17, 20,
|
51
|
+
22, 24, 26, 28, 29, 31, 33, 35})),
|
52
|
+
Play("rouge", "🔴", 1, 2, frozenset({1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21,
|
53
|
+
23, 25, 27, 30, 32, 34, 36})),
|
54
|
+
Play("impaire", "1️⃣", 3, 2, frozenset(range(1, 37, 2))),
|
55
|
+
Play("pair", "2️⃣", 3, 2, frozenset(range(2, 37, 2))),
|
56
|
+
Play("manque", "⬅️", 5, 2, frozenset(range(1, 19))),
|
57
|
+
Play("passe", "➡️", 5, 2, frozenset(range(19, 37))),
|
58
|
+
Play("zero", "0️⃣", 1, 36, frozenset({0})),
|
59
|
+
]
|
60
|
+
play_mapper = {p.label: p for p in plays}
|
61
|
+
# fmt: on
|
62
|
+
|
63
|
+
|
64
|
+
@dataclass
|
65
|
+
class RouletteResult:
|
66
|
+
draw: int
|
67
|
+
winners: dict[discord.Member, Play]
|
68
|
+
losers: dict[discord.Member, Play]
|
69
|
+
|
70
|
+
@property
|
71
|
+
def label(self) -> str:
|
72
|
+
"""Returns the name(s) of the winning bet(s)."""
|
73
|
+
winning_plays = sorted(set(self.winners.values()))
|
74
|
+
if len(winning_plays) == 1:
|
75
|
+
return winning_plays[0].name
|
76
|
+
if winning_plays:
|
77
|
+
last = winning_plays[-1]
|
78
|
+
return (
|
79
|
+
", ".join(p.name for p in winning_plays[:-1])
|
80
|
+
+ " et "
|
81
|
+
+ last.name
|
82
|
+
)
|
83
|
+
return "rien au numéro"
|
84
|
+
|
85
|
+
|
86
|
+
class Roulette:
|
87
|
+
def __init__(self, locker: EggLocker) -> None:
|
88
|
+
"""Initialize an empty bet tracker."""
|
89
|
+
self.bets: dict[discord.Member, Play] = {}
|
90
|
+
self.eggs: dict[discord.Member, list[Egg]] = {}
|
91
|
+
self.locker = locker
|
92
|
+
|
93
|
+
async def bet(self, member: discord.Member, play: Play) -> None:
|
94
|
+
"""Register a bet from a member."""
|
95
|
+
if member in self.eggs:
|
96
|
+
raise ValueError
|
97
|
+
async with self.locker.transaction():
|
98
|
+
eggs = await self.locker.get(member, play.bet)
|
99
|
+
self.eggs[member] = eggs
|
100
|
+
self.bets[member] = play
|
101
|
+
|
102
|
+
async def sample(self) -> "RouletteResult":
|
103
|
+
"""Draw a number and determine winners/losers."""
|
104
|
+
ball = RAND.randint(0, 36)
|
105
|
+
losers = {}
|
106
|
+
winners = {}
|
107
|
+
futures = []
|
108
|
+
async with self.locker.transaction():
|
109
|
+
for member, play in self.bets.items():
|
110
|
+
eggs = self.eggs[member]
|
111
|
+
if ball in play.slots:
|
112
|
+
added_eggs = [
|
113
|
+
egg.duplicate()
|
114
|
+
for egg in eggs
|
115
|
+
for _ in range(play.payout - 1)
|
116
|
+
]
|
117
|
+
self.locker.update(added_eggs)
|
118
|
+
winners[member] = play
|
119
|
+
else:
|
120
|
+
futures.append(self.locker.delete(eggs))
|
121
|
+
losers[member] = play
|
122
|
+
await asyncio.gather(*futures)
|
123
|
+
return RouletteResult(
|
124
|
+
draw=ball,
|
125
|
+
losers=losers,
|
126
|
+
winners=winners,
|
127
|
+
)
|
128
|
+
|
129
|
+
|
130
|
+
class BetView(discord.ui.View):
|
131
|
+
def __init__(self, embed: discord.Embed, roulette: Roulette) -> None:
|
132
|
+
"""Create an interactive view for placing bets."""
|
133
|
+
super().__init__()
|
134
|
+
self.embed = embed
|
135
|
+
self.roulette = roulette
|
136
|
+
self.already_interact: set[discord.Member] = set()
|
137
|
+
|
138
|
+
def disable(self) -> None:
|
139
|
+
"""Disable the selection UI."""
|
140
|
+
self.select_bet.disabled = True # type: ignore[attr-defined]
|
141
|
+
self.stop()
|
142
|
+
|
143
|
+
@discord.ui.select(
|
144
|
+
placeholder="Parier",
|
145
|
+
options=[
|
146
|
+
discord.SelectOption(
|
147
|
+
label=f"Parier {play.label}",
|
148
|
+
emoji=play.emoji,
|
149
|
+
value=play.label,
|
150
|
+
description=(
|
151
|
+
f"{play.probability:.2%} de repartir avec {play.eggs} œufs"
|
152
|
+
),
|
153
|
+
)
|
154
|
+
for play in plays
|
155
|
+
],
|
156
|
+
)
|
157
|
+
async def select_bet(
|
158
|
+
self,
|
159
|
+
interaction: discord.Interaction["Easterobot"],
|
160
|
+
select: discord.ui.Select["BetView"],
|
161
|
+
) -> None:
|
162
|
+
"""Handle the player's bet selection."""
|
163
|
+
user = interaction.user
|
164
|
+
if not isinstance(user, discord.Member) or interaction.message is None:
|
165
|
+
await interaction.response.defer()
|
166
|
+
return
|
167
|
+
if user in self.already_interact:
|
168
|
+
await interaction.response.send_message(
|
169
|
+
"Vous avez déjà choisi votre pari !",
|
170
|
+
ephemeral=True,
|
171
|
+
)
|
172
|
+
return
|
173
|
+
self.already_interact.add(user)
|
174
|
+
bet = play_mapper[select.values[0]]
|
175
|
+
await self.roulette.bet(user, bet)
|
176
|
+
embeds = interaction.message.embeds
|
177
|
+
assert self.embed.description is not None # noqa: S101
|
178
|
+
self.embed.description += (
|
179
|
+
f"\n> {interaction.user.mention} a parié {bet.label} {bet.emoji}"
|
180
|
+
)
|
181
|
+
await interaction.response.edit_message(embeds=[embeds[0], self.embed])
|
182
|
+
|
183
|
+
|
184
|
+
class RouletteManager:
|
185
|
+
def __init__(self, bot: Easterobot) -> None:
|
186
|
+
"""Main manager for roulette game logic."""
|
187
|
+
self.bot = bot
|
188
|
+
|
189
|
+
async def run(
|
190
|
+
self,
|
191
|
+
source: Union[discord.Message, discord.TextChannel],
|
192
|
+
) -> None:
|
193
|
+
"""Run a full roulette session."""
|
194
|
+
guild = source.guild
|
195
|
+
if guild is None:
|
196
|
+
raise ValueError
|
197
|
+
async with (
|
198
|
+
AsyncSession(
|
199
|
+
self.bot.engine,
|
200
|
+
expire_on_commit=False,
|
201
|
+
) as session,
|
202
|
+
EggLocker(session, guild.id) as locker,
|
203
|
+
):
|
204
|
+
timeout = self.bot.config.casino.roulette.duration + 40
|
205
|
+
roulette = Roulette(locker)
|
206
|
+
embed = discord.Embed(
|
207
|
+
description=(
|
208
|
+
"# Roulette lapinique"
|
209
|
+
"\nLe Casino vous ouvre exceptionnellement ses portes. "
|
210
|
+
"Devant vous se trouve un élégant croupier lapin. "
|
211
|
+
"Il vous fixe droit dans les yeux "
|
212
|
+
"et prononce de simples mots en langue lapinique. "
|
213
|
+
"Magiquement, vous semblez comprendre : 'Faites vos jeux'."
|
214
|
+
"\n\n-# Faites attention, "
|
215
|
+
f"il annoncera sans doute la fin {in_seconds(timeout)}."
|
216
|
+
),
|
217
|
+
color=0x00FF00,
|
218
|
+
)
|
219
|
+
text = discord.Embed(
|
220
|
+
description="# Annonces du croupier\n> Faites vos jeux",
|
221
|
+
color=0x00FF00,
|
222
|
+
)
|
223
|
+
assert text.description is not None # noqa: S101
|
224
|
+
embed.set_image(
|
225
|
+
url="https://i.pinimg.com/originals/32/37/bf/3237bf1e172a6089e0c437ffd3b28010.gif"
|
226
|
+
)
|
227
|
+
view = BetView(text, roulette)
|
228
|
+
if isinstance(source, discord.Message):
|
229
|
+
message = source
|
230
|
+
await message.edit(
|
231
|
+
embeds=[embed, text],
|
232
|
+
content="",
|
233
|
+
view=view,
|
234
|
+
)
|
235
|
+
else:
|
236
|
+
message = await source.send(
|
237
|
+
embeds=[embed, text],
|
238
|
+
view=view,
|
239
|
+
)
|
240
|
+
await sleep(timeout)
|
241
|
+
text.description += "\n> Les jeux sont faits"
|
242
|
+
await message.edit(embeds=[embed, text])
|
243
|
+
await sleep(20)
|
244
|
+
view.disable()
|
245
|
+
text.description += "\n> Rien ne va plus"
|
246
|
+
await message.edit(view=view, embeds=[embed, text])
|
247
|
+
await sleep(20)
|
248
|
+
result = await roulette.sample()
|
249
|
+
text.description += "\n> La bille s'arrête "
|
250
|
+
number = f"{result.draw:2d}".replace(" ", "\xa0")
|
251
|
+
text.description += f"sur le ||{number}||"
|
252
|
+
text.description += f"\n> Le lapin annonce ||{result.label}||"
|
253
|
+
await message.edit(view=None, embeds=[embed, text])
|
254
|
+
|
255
|
+
messages = []
|
256
|
+
for member, bet in result.winners.items():
|
257
|
+
egg_text = agree("œuf", "œufs", bet.bet)
|
258
|
+
messages.append(
|
259
|
+
f"{member.mention} repart avec {bet.eggs} {egg_text}"
|
260
|
+
)
|
261
|
+
for member, bet in result.losers.items():
|
262
|
+
egg_text = agree("œuf", "œufs", bet.bet)
|
263
|
+
messages.append(f"{member.mention} perd {bet.bet} {egg_text}")
|
264
|
+
if messages:
|
265
|
+
await sleep(5)
|
266
|
+
await message.reply( # type: ignore[call-overload]
|
267
|
+
content="\n".join(messages),
|
268
|
+
view=None,
|
269
|
+
)
|
@@ -14,6 +14,7 @@ from easterobot.commands.game import (
|
|
14
14
|
from easterobot.commands.help import help_command
|
15
15
|
from easterobot.commands.info import info_command
|
16
16
|
from easterobot.commands.reset import reset_command
|
17
|
+
from easterobot.commands.roulette import roulette_command
|
17
18
|
from easterobot.commands.search import search_command
|
18
19
|
from easterobot.commands.top import top_command
|
19
20
|
|
@@ -28,6 +29,7 @@ __all__ = [
|
|
28
29
|
"info_command",
|
29
30
|
"reset_command",
|
30
31
|
"rockpaperscissor_command",
|
32
|
+
"roulette_command",
|
31
33
|
"search_command",
|
32
34
|
"tictactoe_command",
|
33
35
|
"top_command",
|
@@ -0,0 +1,204 @@
|
|
1
|
+
"""Module for disable hunt."""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
from contextlib import suppress
|
5
|
+
from typing import Optional
|
6
|
+
|
7
|
+
import discord
|
8
|
+
from discord import app_commands
|
9
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
10
|
+
|
11
|
+
from easterobot.config import RAND
|
12
|
+
from easterobot.games.connect4 import Connect4
|
13
|
+
from easterobot.games.game import Game
|
14
|
+
from easterobot.games.rock_paper_scissor import RockPaperScissor
|
15
|
+
from easterobot.games.skyjo import Skyjo
|
16
|
+
from easterobot.games.tic_tac_toe import TicTacToe
|
17
|
+
from easterobot.hunts.rank import Ranking
|
18
|
+
from easterobot.locker import EggLocker, EggLockerError
|
19
|
+
|
20
|
+
from .base import Context, controlled_command, egg_command_group
|
21
|
+
|
22
|
+
|
23
|
+
async def random_members(
|
24
|
+
ctx: Context,
|
25
|
+
bet: int,
|
26
|
+
) -> list[discord.Member]:
|
27
|
+
"""Random members."""
|
28
|
+
# If no member choose a random play in the guild with enough egg
|
29
|
+
if bet == 0:
|
30
|
+
members = [
|
31
|
+
m for m in ctx.guild.members if m.id != ctx.user.id and not m.bot
|
32
|
+
]
|
33
|
+
else:
|
34
|
+
# TODO(dashstrom): can chose member with locked eggs
|
35
|
+
async with AsyncSession(ctx.client.engine) as session:
|
36
|
+
ranking = await Ranking.from_guild(
|
37
|
+
session,
|
38
|
+
ctx.guild_id,
|
39
|
+
unlock_only=True,
|
40
|
+
)
|
41
|
+
hunters = ranking.over(bet)
|
42
|
+
mapper_member = {m.id: m for m in ctx.guild.members}
|
43
|
+
members = [
|
44
|
+
mapper_member[h.member_id]
|
45
|
+
for h in hunters
|
46
|
+
if h.member_id != ctx.user.id and h.member_id in mapper_member
|
47
|
+
]
|
48
|
+
RAND.shuffle(members)
|
49
|
+
return members
|
50
|
+
|
51
|
+
|
52
|
+
async def game_dual( # noqa: D103
|
53
|
+
ctx: Context,
|
54
|
+
bet: int,
|
55
|
+
cls: type[Game],
|
56
|
+
*members: discord.Member,
|
57
|
+
) -> None:
|
58
|
+
set_members = set(members)
|
59
|
+
with suppress(KeyError):
|
60
|
+
set_members.remove(ctx.user)
|
61
|
+
min_player = cls.minimum_player()
|
62
|
+
max_player = cls.maximum_player()
|
63
|
+
if min_player > len(set_members) + 1:
|
64
|
+
await ctx.response.send_message(
|
65
|
+
f"Vous devez être au minimum {min_player} joueurs",
|
66
|
+
ephemeral=True,
|
67
|
+
)
|
68
|
+
return
|
69
|
+
if max_player < len(set_members) + 1:
|
70
|
+
await ctx.response.send_message(
|
71
|
+
f"Vous devez être au maximum {min_player} joueurs",
|
72
|
+
ephemeral=True,
|
73
|
+
)
|
74
|
+
return
|
75
|
+
|
76
|
+
# Check if user has enough eggs for ask
|
77
|
+
async with AsyncSession(
|
78
|
+
ctx.client.engine,
|
79
|
+
expire_on_commit=False,
|
80
|
+
) as session:
|
81
|
+
locker = EggLocker(session, ctx.guild.id)
|
82
|
+
try:
|
83
|
+
await locker.pre_check(
|
84
|
+
{ctx.user: bet, **{m: bet for m in set_members}}
|
85
|
+
)
|
86
|
+
except EggLockerError as err:
|
87
|
+
await ctx.response.send_message(str(err), ephemeral=True)
|
88
|
+
return
|
89
|
+
|
90
|
+
msg = await ctx.client.game.ask_dual(ctx, set_members, bet=bet)
|
91
|
+
if msg:
|
92
|
+
# Unlock all egg at end
|
93
|
+
async with locker:
|
94
|
+
# Lock the egg of player
|
95
|
+
try:
|
96
|
+
async with locker.transaction():
|
97
|
+
all_eggs = await asyncio.gather(
|
98
|
+
locker.get(ctx.user, bet),
|
99
|
+
*[locker.get(m, bet) for m in set_members],
|
100
|
+
)
|
101
|
+
except EggLockerError as err:
|
102
|
+
await msg.reply(str(err), delete_after=30)
|
103
|
+
return
|
104
|
+
|
105
|
+
players = [ctx.user, *set_members]
|
106
|
+
RAND.shuffle(players)
|
107
|
+
game = cls(ctx.client, msg, *players)
|
108
|
+
await ctx.client.game.run(game)
|
109
|
+
winner = await game.wait_winner()
|
110
|
+
if winner:
|
111
|
+
for eggs in all_eggs:
|
112
|
+
for egg in eggs:
|
113
|
+
egg.user_id = winner.member.id
|
114
|
+
|
115
|
+
# Send change
|
116
|
+
await session.commit()
|
117
|
+
|
118
|
+
|
119
|
+
@egg_command_group.command(
|
120
|
+
name="connect4",
|
121
|
+
description="Lancer une partie de puissance 4",
|
122
|
+
)
|
123
|
+
@controlled_command(cooldown=True, channel_permissions={"send_messages": True})
|
124
|
+
async def connect4_command(
|
125
|
+
ctx: Context,
|
126
|
+
member: Optional[discord.Member] = None,
|
127
|
+
bet: app_commands.Range[int, 0] = 0,
|
128
|
+
) -> None:
|
129
|
+
"""Run a Connect4."""
|
130
|
+
members = (
|
131
|
+
(await random_members(ctx, bet))[:1] if member is None else [member]
|
132
|
+
)
|
133
|
+
await game_dual(ctx, bet, Connect4, *members)
|
134
|
+
|
135
|
+
|
136
|
+
@egg_command_group.command(
|
137
|
+
name="tictactoe",
|
138
|
+
description="Lancer une partie de morpion",
|
139
|
+
)
|
140
|
+
@controlled_command(cooldown=True, channel_permissions={"send_messages": True})
|
141
|
+
async def tictactoe_command(
|
142
|
+
ctx: Context,
|
143
|
+
member: Optional[discord.Member] = None,
|
144
|
+
bet: app_commands.Range[int, 0] = 0,
|
145
|
+
) -> None:
|
146
|
+
"""Run a tictactoe."""
|
147
|
+
members = (
|
148
|
+
(await random_members(ctx, bet))[:1] if member is None else [member]
|
149
|
+
)
|
150
|
+
await game_dual(ctx, bet, TicTacToe, *members)
|
151
|
+
|
152
|
+
|
153
|
+
@egg_command_group.command(
|
154
|
+
name="rockpaperscissor",
|
155
|
+
description="Lancer une partie de pierre papier ciseaux",
|
156
|
+
)
|
157
|
+
@controlled_command(cooldown=True, channel_permissions={"send_messages": True})
|
158
|
+
async def rockpaperscissor_command(
|
159
|
+
ctx: Context,
|
160
|
+
member: Optional[discord.Member] = None,
|
161
|
+
bet: app_commands.Range[int, 0] = 0,
|
162
|
+
) -> None:
|
163
|
+
"""Run a rockpaperscissor."""
|
164
|
+
members = (
|
165
|
+
(await random_members(ctx, bet))[:1] if member is None else [member]
|
166
|
+
)
|
167
|
+
await game_dual(ctx, bet, RockPaperScissor, *members)
|
168
|
+
|
169
|
+
|
170
|
+
@egg_command_group.command(
|
171
|
+
name="skyjo",
|
172
|
+
description="Lancer une partie de Skyjo",
|
173
|
+
)
|
174
|
+
@controlled_command(cooldown=True, channel_permissions={"send_messages": True})
|
175
|
+
async def skyjo_command( # noqa: PLR0913
|
176
|
+
ctx: Context,
|
177
|
+
member1: Optional[discord.Member] = None,
|
178
|
+
member2: Optional[discord.Member] = None,
|
179
|
+
member3: Optional[discord.Member] = None,
|
180
|
+
member4: Optional[discord.Member] = None,
|
181
|
+
member5: Optional[discord.Member] = None,
|
182
|
+
member6: Optional[discord.Member] = None,
|
183
|
+
member7: Optional[discord.Member] = None,
|
184
|
+
bet: app_commands.Range[int, 0] = 0,
|
185
|
+
) -> None:
|
186
|
+
"""Run a skyjo."""
|
187
|
+
members = [
|
188
|
+
m
|
189
|
+
for m in (
|
190
|
+
member1,
|
191
|
+
member2,
|
192
|
+
member3,
|
193
|
+
member4,
|
194
|
+
member5,
|
195
|
+
member6,
|
196
|
+
member7,
|
197
|
+
)
|
198
|
+
if m
|
199
|
+
]
|
200
|
+
if not members:
|
201
|
+
player_count = RAND.randint(1, 8)
|
202
|
+
rand_members = await random_members(ctx, bet)
|
203
|
+
members = rand_members[:player_count]
|
204
|
+
await game_dual(ctx, bet, Skyjo, *members)
|