easterobot 1.1.2__tar.gz → 1.3.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.1.2 → easterobot-1.3.2}/PKG-INFO +9 -5
- {easterobot-1.1.2 → easterobot-1.3.2}/README.rst +8 -4
- easterobot-1.3.2/conftest.py +44 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/docker-compose.yml +1 -1
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/bot.py +15 -6
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/commands/__init__.py +3 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/commands/base.py +4 -1
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/commands/disable.py +1 -1
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/commands/enable.py +1 -1
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/commands/game.py +27 -25
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/commands/help.py +3 -3
- easterobot-1.3.2/easterobot/commands/info.py +61 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/commands/reset.py +1 -1
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/commands/search.py +14 -40
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/config.py +71 -1
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/games/connect.py +8 -7
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/games/game.py +105 -34
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/games/rock_paper_scissor.py +22 -8
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/games/tic_tac_toe.py +8 -7
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/hunts/hunt.py +183 -84
- easterobot-1.3.2/easterobot/hunts/luck.py +58 -0
- easterobot-1.3.2/easterobot/query.py +11 -0
- easterobot-1.3.2/easterobot/resources/config.example.yml +443 -0
- easterobot-1.3.2/easterobot/utils.py +11 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/pyproject.toml +8 -6
- easterobot-1.3.2/tests/constants.py +8 -0
- easterobot-1.3.2/tests/test_search.py +54 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/tools/chatgpt.txt +2 -2
- {easterobot-1.1.2 → easterobot-1.3.2}/uv.lock +27 -1
- easterobot-1.1.2/conftest.py +0 -13
- easterobot-1.1.2/easterobot/resources/config.example.yml +0 -234
- {easterobot-1.1.2 → easterobot-1.3.2}/.dockerignore +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/.editorconfig +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/.github/actions/setup-project/action.yml +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/.github/workflows/docs.yml +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/.github/workflows/lint.yml +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/.github/workflows/publish.yml +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/.github/workflows/tests.yml +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/.gitignore +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/.pre-commit-config.yaml +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/.vscode/extensions.json +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/.vscode/ltex.dictionary.en-US.txt +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/.vscode/ltex.hiddenFalsePositives.en-US.txt +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/.vscode/settings.json +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/Dockerfile +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/LICENSE +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/docs/conf.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/docs/index.rst +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/docs/references.rst +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/docs/resources/favicon.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/__init__.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/__main__.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/alembic/env.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/alembic/script.py.mako +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/alembic/versions/2f0d4305e320_init_database.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/alembic/versions/940c3b9c702d_add_lock_on_eggs.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/cli.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/commands/basket.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/commands/edit.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/commands/top.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/games/__init__.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/hunts/__init__.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/hunts/rank.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/info.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/logger.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/models.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/py.typed +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/alembic.ini +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/credits.txt +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_01.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_02.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_03.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_04.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_05.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_06.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_07.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_08.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_09.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_10.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_11.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_12.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_13.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_14.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_15.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_16.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_17.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_18.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_19.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/eggs/egg_20.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/icons/arrow.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/icons/end.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/icons/versus.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/emotes/icons/wait.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/logging.conf +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/easterobot/resources/logo.png +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/entrypoint.sh +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/tests/__init__.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/tests/test_cli.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.2}/tests/test_config.py +0 -0
- {easterobot-1.1.2 → easterobot-1.3.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.3.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
|
@@ -121,10 +121,8 @@ from `PyPI <https://pypi.org/project>`_
|
|
121
121
|
cd easterobot
|
122
122
|
echo "DISCORD_TOKEN=YOU_MUST_PUT_YOUR_TOKEN_HERE" > .env
|
123
123
|
|
124
|
-
# Can be unsafe
|
125
|
-
chmod -R 700 .
|
126
|
-
mkdir data
|
127
|
-
chmod 777 data
|
124
|
+
# Can be unsafe (and for each update)
|
125
|
+
chmod -R 700 . && mkdir data -p && chmod 777 data
|
128
126
|
|
129
127
|
# Run the docker container
|
130
128
|
docker compose up -d
|
@@ -135,6 +133,12 @@ from `PyPI <https://pypi.org/project>`_
|
|
135
133
|
# Remove the container (not the data)
|
136
134
|
docker compose down --rmi all
|
137
135
|
|
136
|
+
# Update
|
137
|
+
git reset --hard HEAD && git pull
|
138
|
+
|
139
|
+
# One-line update
|
140
|
+
docker compose down --rmi all && git reset --hard HEAD && git pull && chmod -R 700 . && mkdir data -p && chmod 777 data && docker compose up -d
|
141
|
+
|
138
142
|
Configuration directory
|
139
143
|
#######################
|
140
144
|
|
@@ -83,10 +83,8 @@ from `PyPI <https://pypi.org/project>`_
|
|
83
83
|
cd easterobot
|
84
84
|
echo "DISCORD_TOKEN=YOU_MUST_PUT_YOUR_TOKEN_HERE" > .env
|
85
85
|
|
86
|
-
# Can be unsafe
|
87
|
-
chmod -R 700 .
|
88
|
-
mkdir data
|
89
|
-
chmod 777 data
|
86
|
+
# Can be unsafe (and for each update)
|
87
|
+
chmod -R 700 . && mkdir data -p && chmod 777 data
|
90
88
|
|
91
89
|
# Run the docker container
|
92
90
|
docker compose up -d
|
@@ -97,6 +95,12 @@ from `PyPI <https://pypi.org/project>`_
|
|
97
95
|
# Remove the container (not the data)
|
98
96
|
docker compose down --rmi all
|
99
97
|
|
98
|
+
# Update
|
99
|
+
git reset --hard HEAD && git pull
|
100
|
+
|
101
|
+
# One-line update
|
102
|
+
docker compose down --rmi all && git reset --hard HEAD && git pull && chmod -R 700 . && mkdir data -p && chmod 777 data && docker compose up -d
|
103
|
+
|
100
104
|
Configuration directory
|
101
105
|
#######################
|
102
106
|
|
@@ -0,0 +1,44 @@
|
|
1
|
+
"""Configuration for all tests."""
|
2
|
+
|
3
|
+
from collections.abc import AsyncIterator
|
4
|
+
from typing import Any
|
5
|
+
|
6
|
+
import py
|
7
|
+
import pytest
|
8
|
+
import pytest_asyncio
|
9
|
+
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
|
10
|
+
|
11
|
+
from easterobot import __author__
|
12
|
+
from easterobot.bot import Easterobot
|
13
|
+
from easterobot.config import MConfig
|
14
|
+
|
15
|
+
|
16
|
+
@pytest.fixture(autouse=True)
|
17
|
+
def _add_author(doctest_namespace: dict[str, Any]) -> None:
|
18
|
+
"""Update doctest namespace."""
|
19
|
+
doctest_namespace["author"] = __author__
|
20
|
+
|
21
|
+
|
22
|
+
@pytest.fixture
|
23
|
+
def bot(tmpdir: py.path.LocalPath) -> Easterobot:
|
24
|
+
"""Get a bot ready-to-use."""
|
25
|
+
return Easterobot.generate(str(tmpdir), env=True)
|
26
|
+
|
27
|
+
|
28
|
+
@pytest.fixture
|
29
|
+
def engine(bot: Easterobot) -> AsyncEngine:
|
30
|
+
"""Get a bot ready-to-use."""
|
31
|
+
return bot.engine
|
32
|
+
|
33
|
+
|
34
|
+
@pytest_asyncio.fixture
|
35
|
+
async def session(engine: AsyncEngine) -> AsyncIterator[AsyncSession]:
|
36
|
+
"""Get a bot ready-to-use."""
|
37
|
+
async with AsyncSession(engine, expire_on_commit=False) as session:
|
38
|
+
yield session
|
39
|
+
|
40
|
+
|
41
|
+
@pytest.fixture
|
42
|
+
def config(bot: Easterobot) -> MConfig:
|
43
|
+
"""Get a bot ready-to-use."""
|
44
|
+
return bot.config
|
@@ -1,7 +1,6 @@
|
|
1
1
|
"""Main program."""
|
2
2
|
|
3
3
|
import logging
|
4
|
-
import logging.config
|
5
4
|
import pathlib
|
6
5
|
import shutil
|
7
6
|
from getpass import getpass
|
@@ -37,7 +36,8 @@ from .config import (
|
|
37
36
|
T = TypeVar("T")
|
38
37
|
|
39
38
|
logger = logging.getLogger(__name__)
|
40
|
-
INTENTS = discord.Intents.
|
39
|
+
INTENTS = discord.Intents.default()
|
40
|
+
INTENTS.message_content = True
|
41
41
|
|
42
42
|
|
43
43
|
class Easterobot(discord.ext.commands.Bot):
|
@@ -47,15 +47,24 @@ class Easterobot(discord.ext.commands.Bot):
|
|
47
47
|
|
48
48
|
def __init__(self, config: MConfig) -> None:
|
49
49
|
"""Initialise Easterbot."""
|
50
|
+
# Initialize intents
|
51
|
+
intents = discord.Intents.default()
|
52
|
+
if config.message_content:
|
53
|
+
intents.message_content = True
|
54
|
+
|
55
|
+
# Remove warning about VoiceClient
|
56
|
+
discord.VoiceClient.warn_nacl = False
|
57
|
+
|
58
|
+
# Initialize Bot
|
50
59
|
super().__init__(
|
51
60
|
command_prefix=".",
|
52
61
|
description="Bot discord pour faire la chasse aux œufs",
|
53
62
|
activity=discord.Game(name="rechercher des œufs"),
|
54
63
|
intents=INTENTS,
|
55
64
|
)
|
56
|
-
self.config = config
|
57
65
|
|
58
66
|
# Attributes
|
67
|
+
self.config = config
|
59
68
|
self.app_commands: list[discord.app_commands.AppCommand] = []
|
60
69
|
self.app_emojis: dict[str, discord.Emoji] = {}
|
61
70
|
|
@@ -89,9 +98,9 @@ class Easterobot(discord.ext.commands.Bot):
|
|
89
98
|
cls,
|
90
99
|
destination: Union[Path, str],
|
91
100
|
*,
|
92
|
-
token: Optional[str],
|
93
|
-
env: bool,
|
94
|
-
interactive: bool,
|
101
|
+
token: Optional[str] = None,
|
102
|
+
env: bool = False,
|
103
|
+
interactive: bool = False,
|
95
104
|
) -> "Easterobot":
|
96
105
|
"""Generate all data."""
|
97
106
|
destination = Path(destination).resolve()
|
@@ -12,6 +12,7 @@ from easterobot.commands.game import (
|
|
12
12
|
tictactoe_command,
|
13
13
|
)
|
14
14
|
from easterobot.commands.help import help_command
|
15
|
+
from easterobot.commands.info import info_command
|
15
16
|
from easterobot.commands.reset import reset_command
|
16
17
|
from easterobot.commands.search import search_command
|
17
18
|
from easterobot.commands.top import top_command
|
@@ -24,6 +25,7 @@ __all__ = [
|
|
24
25
|
"egg_command_group",
|
25
26
|
"enable_command",
|
26
27
|
"help_command",
|
28
|
+
"info_command",
|
27
29
|
"reset_command",
|
28
30
|
"rockpaperscissor_command",
|
29
31
|
"search_command",
|
@@ -33,4 +35,5 @@ __all__ = [
|
|
33
35
|
|
34
36
|
|
35
37
|
async def setup(bot: Easterobot) -> None:
|
38
|
+
egg_command_group.name = bot.config.group
|
36
39
|
bot.tree.add_command(egg_command_group)
|
@@ -170,7 +170,10 @@ def controlled_command( # noqa: C901, PLR0915
|
|
170
170
|
try:
|
171
171
|
await f(cast(Context, interaction), *args, **kwargs)
|
172
172
|
except InterruptedCommandError:
|
173
|
-
logger.
|
173
|
+
logger.warning(
|
174
|
+
"InterruptedCommandError occur for %s",
|
175
|
+
event_repr,
|
176
|
+
)
|
174
177
|
async with (
|
175
178
|
AsyncSession(interaction.client.engine) as session,
|
176
179
|
cooldown_lock,
|
@@ -10,7 +10,7 @@ from .base import Context, controlled_command, egg_command_group
|
|
10
10
|
|
11
11
|
@egg_command_group.command(
|
12
12
|
name="disable",
|
13
|
-
description="Désactiver la chasse aux œufs dans le salon"
|
13
|
+
description="Désactiver la chasse aux œufs dans le salon",
|
14
14
|
)
|
15
15
|
@controlled_command(cooldown=True, manage_channels=True)
|
16
16
|
async def disable_command(ctx: Context) -> None:
|
@@ -10,7 +10,7 @@ from .base import Context, controlled_command, egg_command_group
|
|
10
10
|
|
11
11
|
@egg_command_group.command(
|
12
12
|
name="enable",
|
13
|
-
description="Activer la chasse dans le salon"
|
13
|
+
description="Activer la chasse dans le salon",
|
14
14
|
)
|
15
15
|
@controlled_command(
|
16
16
|
cooldown=True,
|
@@ -91,13 +91,13 @@ async def game_dual( # noqa: C901, D103, PLR0912
|
|
91
91
|
)
|
92
92
|
if len(e1) < bet:
|
93
93
|
await ctx.response.send_message(
|
94
|
-
"Vous n'avez pas assez d'
|
94
|
+
"Vous n'avez pas assez d'œufs",
|
95
95
|
ephemeral=True,
|
96
96
|
)
|
97
97
|
return
|
98
98
|
if len(e2) < bet:
|
99
99
|
await ctx.response.send_message(
|
100
|
-
f"{member.mention} n'a pas assez d'
|
100
|
+
f"{member.mention} n'a pas assez d'œufs",
|
101
101
|
ephemeral=True,
|
102
102
|
)
|
103
103
|
return
|
@@ -113,7 +113,7 @@ async def game_dual( # noqa: C901, D103, PLR0912
|
|
113
113
|
)
|
114
114
|
if len(e1) < bet:
|
115
115
|
await ctx.response.send_message(
|
116
|
-
"Vous n'avez plus assez d'
|
116
|
+
"Vous n'avez plus assez d'œufs",
|
117
117
|
ephemeral=True,
|
118
118
|
)
|
119
119
|
return
|
@@ -121,7 +121,7 @@ async def game_dual( # noqa: C901, D103, PLR0912
|
|
121
121
|
e.lock = True
|
122
122
|
if len(e2) < bet:
|
123
123
|
await ctx.response.send_message(
|
124
|
-
f"{member.mention} n'a plus assez d'
|
124
|
+
f"{member.mention} n'a plus assez d'œufs",
|
125
125
|
ephemeral=True,
|
126
126
|
)
|
127
127
|
return
|
@@ -130,26 +130,28 @@ async def game_dual( # noqa: C901, D103, PLR0912
|
|
130
130
|
await session.commit()
|
131
131
|
|
132
132
|
# Play the game
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
e.
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
e.
|
147
|
-
|
133
|
+
winner = None
|
134
|
+
try:
|
135
|
+
game = cls(ctx.user, member, msg)
|
136
|
+
await ctx.client.game.run(game)
|
137
|
+
winner = await game.wait_winner()
|
138
|
+
finally:
|
139
|
+
# Give eggs to the winner or remove previous one
|
140
|
+
async with lock:
|
141
|
+
for e in e1:
|
142
|
+
e.lock = False
|
143
|
+
if winner:
|
144
|
+
e.user_id = winner.id
|
145
|
+
for e in e2:
|
146
|
+
e.lock = False
|
147
|
+
if winner:
|
148
|
+
e.user_id = winner.id
|
149
|
+
await session.commit()
|
148
150
|
|
149
151
|
|
150
152
|
@egg_command_group.command(
|
151
153
|
name="connect4",
|
152
|
-
description="Lancer une partie de puissance 4"
|
154
|
+
description="Lancer une partie de puissance 4",
|
153
155
|
)
|
154
156
|
@controlled_command(cooldown=True, channel_permissions={"send_messages": True})
|
155
157
|
async def connect4_command(
|
@@ -163,12 +165,12 @@ async def connect4_command(
|
|
163
165
|
|
164
166
|
@egg_command_group.command(
|
165
167
|
name="tictactoe",
|
166
|
-
description="Lancer une partie de morpion"
|
168
|
+
description="Lancer une partie de morpion",
|
167
169
|
)
|
168
|
-
@controlled_command(cooldown=True)
|
170
|
+
@controlled_command(cooldown=True, channel_permissions={"send_messages": True})
|
169
171
|
async def tictactoe_command(
|
170
172
|
ctx: Context,
|
171
|
-
member: discord.Member,
|
173
|
+
member: Optional[discord.Member] = None,
|
172
174
|
bet: app_commands.Range[int, 0] = 0,
|
173
175
|
) -> None:
|
174
176
|
"""Run a tictactoe."""
|
@@ -179,10 +181,10 @@ async def tictactoe_command(
|
|
179
181
|
name="rockpaperscissor",
|
180
182
|
description="Lancer une partie de pierre papier ciseaux",
|
181
183
|
)
|
182
|
-
@controlled_command(cooldown=True)
|
184
|
+
@controlled_command(cooldown=True, channel_permissions={"send_messages": True})
|
183
185
|
async def rockpaperscissor_command(
|
184
186
|
ctx: Context,
|
185
|
-
member: discord.Member,
|
187
|
+
member: Optional[discord.Member] = None,
|
186
188
|
bet: app_commands.Range[int, 0] = 0,
|
187
189
|
) -> None:
|
188
190
|
"""Run a rockpaperscissor."""
|
@@ -9,9 +9,9 @@ from .base import Context, controlled_command, egg_command_group
|
|
9
9
|
|
10
10
|
@egg_command_group.command(
|
11
11
|
name="help",
|
12
|
-
description="Obtenir l'aide des commandes"
|
12
|
+
description="Obtenir l'aide des commandes",
|
13
13
|
)
|
14
|
-
@controlled_command(cooldown=True)
|
14
|
+
@controlled_command(cooldown=True, channel_permissions={"send_messages": True})
|
15
15
|
async def help_command(ctx: Context) -> None:
|
16
16
|
"""Help command."""
|
17
17
|
emb = embed(
|
@@ -33,4 +33,4 @@ async def help_command(ctx: Context) -> None:
|
|
33
33
|
value=f"{option.description}",
|
34
34
|
inline=False,
|
35
35
|
)
|
36
|
-
await ctx.response.send_message(embed=emb
|
36
|
+
await ctx.response.send_message(embed=emb)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
"""Command basket."""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
from typing import Optional
|
5
|
+
|
6
|
+
import discord
|
7
|
+
from discord import app_commands
|
8
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
9
|
+
|
10
|
+
from easterobot.commands.base import (
|
11
|
+
Context,
|
12
|
+
controlled_command,
|
13
|
+
egg_command_group,
|
14
|
+
)
|
15
|
+
from easterobot.hunts.hunt import embed
|
16
|
+
from easterobot.hunts.rank import Ranking
|
17
|
+
|
18
|
+
|
19
|
+
@egg_command_group.command(
|
20
|
+
name="info",
|
21
|
+
description="Avoir des informations sur la chance d'un joueur",
|
22
|
+
)
|
23
|
+
@app_commands.describe(
|
24
|
+
user="Joueur a inspecter",
|
25
|
+
)
|
26
|
+
@controlled_command(cooldown=True)
|
27
|
+
async def info_command(
|
28
|
+
ctx: Context, user: Optional[discord.Member] = None
|
29
|
+
) -> None:
|
30
|
+
"""Show current user basket."""
|
31
|
+
# Delay the response
|
32
|
+
await ctx.response.defer(ephemeral=True)
|
33
|
+
|
34
|
+
# Set the user of the basket
|
35
|
+
hunter = user or ctx.user
|
36
|
+
|
37
|
+
async with AsyncSession(ctx.client.engine) as session:
|
38
|
+
ranking, member_luck = await asyncio.gather(
|
39
|
+
Ranking.from_guild(session, ctx.guild_id),
|
40
|
+
ctx.client.hunt.get_luck(
|
41
|
+
session=session,
|
42
|
+
guild_id=hunter.guild.id,
|
43
|
+
user_id=hunter.id,
|
44
|
+
sleep_hours=False,
|
45
|
+
),
|
46
|
+
)
|
47
|
+
hunter_rank = ranking.get(hunter.id)
|
48
|
+
|
49
|
+
await ctx.followup.send(
|
50
|
+
embed=embed(
|
51
|
+
title=f"Informations sur {hunter.display_name}",
|
52
|
+
description=(
|
53
|
+
f"Classement : {hunter_rank.badge}\n"
|
54
|
+
f"Nombre d'œufs : `{hunter_rank.eggs}`\n"
|
55
|
+
f"Chance brute : `{member_luck.luck:.0%}`\n"
|
56
|
+
f"Chance de trouver un œuf : `{member_luck.discovered:.0%}`\n"
|
57
|
+
f"Chance de se faire voler : `{member_luck.spotted:.0%}`"
|
58
|
+
),
|
59
|
+
),
|
60
|
+
ephemeral=True,
|
61
|
+
)
|
@@ -15,7 +15,7 @@ from .base import Context, Interaction, controlled_command, egg_command_group
|
|
15
15
|
|
16
16
|
@egg_command_group.command(
|
17
17
|
name="reset",
|
18
|
-
description="Réinitialiser la chasse aux œufs"
|
18
|
+
description="Réinitialiser la chasse aux œufs",
|
19
19
|
)
|
20
20
|
@controlled_command(cooldown=True, administrator=True)
|
21
21
|
async def reset_command(ctx: Context) -> None:
|
@@ -4,10 +4,10 @@ import logging
|
|
4
4
|
from typing import Any
|
5
5
|
|
6
6
|
import discord
|
7
|
-
from sqlalchemy import
|
7
|
+
from sqlalchemy import select
|
8
8
|
from sqlalchemy.ext.asyncio import AsyncSession
|
9
9
|
|
10
|
-
from easterobot.config import
|
10
|
+
from easterobot.config import agree
|
11
11
|
from easterobot.hunts.hunt import embed
|
12
12
|
from easterobot.models import Egg, Hunt
|
13
13
|
|
@@ -23,7 +23,7 @@ logger = logging.getLogger("easterobot")
|
|
23
23
|
|
24
24
|
@egg_command_group.command(
|
25
25
|
name="search",
|
26
|
-
description="Rechercher un œuf"
|
26
|
+
description="Rechercher un œuf",
|
27
27
|
)
|
28
28
|
@controlled_command(cooldown=True, channel_permissions={"send_messages": True})
|
29
29
|
async def search_command(ctx: Context) -> None:
|
@@ -37,7 +37,7 @@ async def search_command(ctx: Context) -> None:
|
|
37
37
|
"La chasse aux œufs n'est pas activée dans ce salon",
|
38
38
|
ephemeral=True,
|
39
39
|
)
|
40
|
-
|
40
|
+
raise InterruptedCommandError
|
41
41
|
try:
|
42
42
|
await ctx.response.defer(ephemeral=False)
|
43
43
|
except discord.errors.NotFound as err:
|
@@ -45,39 +45,15 @@ async def search_command(ctx: Context) -> None:
|
|
45
45
|
name = ctx.user.display_name
|
46
46
|
|
47
47
|
async with AsyncSession(ctx.client.engine) as session:
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
.
|
53
|
-
.group_by(Egg.user_id)
|
54
|
-
.order_by(func.count().label("max").desc())
|
55
|
-
.limit(1)
|
48
|
+
luck = await ctx.client.hunt.get_luck(
|
49
|
+
guild_id=ctx.guild_id,
|
50
|
+
user_id=ctx.user.id,
|
51
|
+
session=session,
|
52
|
+
sleep_hours=ctx.client.config.in_sleep_hours(),
|
56
53
|
)
|
57
|
-
egg_max = egg_max or 0
|
58
|
-
egg_count = await session.scalar(
|
59
|
-
select(func.count().label("count")).where(
|
60
|
-
and_(
|
61
|
-
Egg.guild_id == ctx.guild.id,
|
62
|
-
Egg.user_id == ctx.user.id,
|
63
|
-
)
|
64
|
-
)
|
65
|
-
)
|
66
|
-
if egg_count is None:
|
67
|
-
egg_count = 0
|
68
|
-
ratio = egg_count / egg_max if egg_max != 0 else 1.0
|
69
|
-
|
70
|
-
discovered = ctx.client.config.commands.search.discovered
|
71
|
-
prob_d = (discovered.max - discovered.min) * (1 - ratio) + discovered.min
|
72
54
|
|
73
|
-
|
74
|
-
|
75
|
-
sample_s = RAND.random()
|
76
|
-
spotted = ctx.client.config.commands.search.spotted
|
77
|
-
prob_s = (spotted.max - spotted.min) * ratio + spotted.min
|
78
|
-
logger.info("discovered: %.2f > %.2f", prob_d, sample_d)
|
79
|
-
if prob_s > sample_s and egg_count > spotted.shield:
|
80
|
-
logger.info("spotted: %.2f > %.2f", prob_s, sample_s)
|
55
|
+
if luck.sample_discovered():
|
56
|
+
if luck.sample_spotted():
|
81
57
|
|
82
58
|
async def send_method(
|
83
59
|
*args: Any, **kwargs: Any
|
@@ -91,7 +67,6 @@ async def search_command(ctx: Context) -> None:
|
|
91
67
|
send_method=send_method,
|
92
68
|
)
|
93
69
|
else:
|
94
|
-
logger.info("found: %.2f > %.2f", prob_s, sample_s)
|
95
70
|
emoji = ctx.client.egg_emotes.rand()
|
96
71
|
async with AsyncSession(ctx.client.engine) as session:
|
97
72
|
session.add(
|
@@ -108,7 +83,7 @@ async def search_command(ctx: Context) -> None:
|
|
108
83
|
"%s (%s) got an egg for a total %s in %s",
|
109
84
|
ctx.user,
|
110
85
|
ctx.user.id,
|
111
|
-
agree("{0} egg", "{0} eggs", egg_count),
|
86
|
+
agree("{0} egg", "{0} eggs", luck.egg_count),
|
112
87
|
ctx.channel.jump_url,
|
113
88
|
)
|
114
89
|
await ctx.followup.send(
|
@@ -116,15 +91,14 @@ async def search_command(ctx: Context) -> None:
|
|
116
91
|
title=f"{name} récupère un œuf",
|
117
92
|
description=ctx.client.config.hidden(ctx.user),
|
118
93
|
thumbnail=emoji.url,
|
119
|
-
egg_count=egg_count + 1,
|
94
|
+
egg_count=luck.egg_count + 1,
|
120
95
|
)
|
121
96
|
)
|
122
97
|
else:
|
123
|
-
logger.info("failed: %.2f > %.2f", prob_d, sample_d)
|
124
98
|
await ctx.followup.send(
|
125
99
|
embed=embed(
|
126
100
|
title=f"{name} repart bredouille",
|
127
101
|
description=ctx.client.config.failed(ctx.user),
|
128
|
-
egg_count=egg_count,
|
102
|
+
egg_count=luck.egg_count,
|
129
103
|
)
|
130
104
|
)
|
@@ -5,9 +5,11 @@ import logging.config
|
|
5
5
|
import os
|
6
6
|
import pathlib
|
7
7
|
import random
|
8
|
+
import re
|
8
9
|
from abc import ABC, abstractmethod
|
9
10
|
from argparse import Namespace
|
10
11
|
from collections.abc import Iterable
|
12
|
+
from datetime import datetime, time, timezone
|
11
13
|
from typing import (
|
12
14
|
Any,
|
13
15
|
Generic,
|
@@ -80,7 +82,7 @@ class ConjugableText(Serializable[str]):
|
|
80
82
|
def gender(member: discord.Member) -> Literal["man", "woman"]:
|
81
83
|
"""Get the gender of a people."""
|
82
84
|
if any(
|
83
|
-
marker in role.name
|
85
|
+
marker in tokenize(role.name)
|
84
86
|
for role in member.roles
|
85
87
|
for marker in (
|
86
88
|
"woman",
|
@@ -88,6 +90,7 @@ class ConjugableText(Serializable[str]):
|
|
88
90
|
"femme",
|
89
91
|
"fille",
|
90
92
|
"elle",
|
93
|
+
"elles",
|
91
94
|
"her",
|
92
95
|
"she",
|
93
96
|
)
|
@@ -163,6 +166,14 @@ class RandomConjugableText(RandomItem[ConjugableText]):
|
|
163
166
|
return cls(convert(obj, typ=list[ConjugableText]))
|
164
167
|
|
165
168
|
|
169
|
+
class MSleep(msgspec.Struct):
|
170
|
+
start: time
|
171
|
+
end: time
|
172
|
+
divide_hunt: float
|
173
|
+
divide_discovered: float
|
174
|
+
divide_spotted: float
|
175
|
+
|
176
|
+
|
166
177
|
class MCooldown(msgspec.Struct):
|
167
178
|
min: float
|
168
179
|
max: float
|
@@ -194,12 +205,20 @@ class MDiscovered(msgspec.Struct):
|
|
194
205
|
min: float
|
195
206
|
max: float
|
196
207
|
|
208
|
+
def probability(self, luck: float) -> float:
|
209
|
+
"""Get discover probability."""
|
210
|
+
return (self.max - self.min) * luck + self.min
|
211
|
+
|
197
212
|
|
198
213
|
class MSpotted(msgspec.Struct):
|
199
214
|
shield: int
|
200
215
|
min: float
|
201
216
|
max: float
|
202
217
|
|
218
|
+
def probability(self, luck: float) -> float:
|
219
|
+
"""Get discover probability."""
|
220
|
+
return (self.max - self.min) * (1 - luck) + self.min
|
221
|
+
|
203
222
|
|
204
223
|
class SearchCommand(MCommand):
|
205
224
|
discovered: MDiscovered
|
@@ -239,6 +258,7 @@ class MCommands(msgspec.Struct, forbid_unknown_fields=True):
|
|
239
258
|
help: MCommand
|
240
259
|
edit: MCommand
|
241
260
|
connect4: MCommand
|
261
|
+
info: MCommand
|
242
262
|
tictactoe: MCommand
|
243
263
|
rockpaperscissor: MCommand
|
244
264
|
|
@@ -269,6 +289,16 @@ class MConfig(msgspec.Struct, dict=True):
|
|
269
289
|
appear: RandomItem[str]
|
270
290
|
action: RandomItem[MText]
|
271
291
|
commands: MCommands
|
292
|
+
sleep: MSleep = msgspec.field(
|
293
|
+
default_factory=lambda: MSleep(
|
294
|
+
start=time(hour=23),
|
295
|
+
end=time(hour=9),
|
296
|
+
divide_hunt=2.0,
|
297
|
+
divide_discovered=2.0,
|
298
|
+
divide_spotted=1.5,
|
299
|
+
)
|
300
|
+
)
|
301
|
+
message_content: bool = True
|
272
302
|
token: Optional[Union[str, msgspec.UnsetType]] = msgspec.UNSET
|
273
303
|
_resources: Optional[Union[pathlib.Path, msgspec.UnsetType]] = (
|
274
304
|
msgspec.field(name="resources", default=msgspec.UNSET)
|
@@ -284,6 +314,17 @@ class MConfig(msgspec.Struct, dict=True):
|
|
284
314
|
"%(data)s", "/" + self.working_directory.as_posix()
|
285
315
|
)
|
286
316
|
|
317
|
+
def in_sleep_hours(self) -> bool:
|
318
|
+
"""Get if bot is currently in sleep mode."""
|
319
|
+
hour = datetime.now(tz=timezone.utc).time()
|
320
|
+
if self.sleep.start < self.sleep.end:
|
321
|
+
if self.sleep.start < hour < self.sleep.end:
|
322
|
+
return True
|
323
|
+
elif self.sleep.start > self.sleep.end: # noqa: SIM102
|
324
|
+
if not self.sleep.start < hour < self.sleep.end:
|
325
|
+
return True
|
326
|
+
return False
|
327
|
+
|
287
328
|
def verified_token(self) -> str:
|
288
329
|
"""Get the safe token."""
|
289
330
|
if self.token is None or self.token is msgspec.UNSET:
|
@@ -370,6 +411,14 @@ class MConfig(msgspec.Struct, dict=True):
|
|
370
411
|
)
|
371
412
|
self.__logging_flag = True
|
372
413
|
|
414
|
+
def __str__(self) -> str:
|
415
|
+
"""Represent the Configuration."""
|
416
|
+
return f"<Config {str(self.working_directory)!r}>"
|
417
|
+
|
418
|
+
def __repr__(self) -> str:
|
419
|
+
"""Represent the Configuration."""
|
420
|
+
return f"<Config {str(self.working_directory)!r}>"
|
421
|
+
|
373
422
|
|
374
423
|
def _dec_hook(typ: type[T], obj: Any) -> T:
|
375
424
|
# Get the base type
|
@@ -463,3 +512,24 @@ def agree(
|
|
463
512
|
if amount is None or amount in (-1, 0, 1):
|
464
513
|
return singular.format(amount, *args)
|
465
514
|
return plural.format(amount, *args)
|
515
|
+
|
516
|
+
|
517
|
+
RE_VALID = re.compile(r"[^a-zA-Z0-9éàèê]")
|
518
|
+
|
519
|
+
|
520
|
+
def tokenize(text: str) -> list[str]:
|
521
|
+
"""Get token from text.
|
522
|
+
|
523
|
+
Examples:
|
524
|
+
>>> tokenize("Activités manuelles")
|
525
|
+
['activités', 'manuelles']
|
526
|
+
>>> tokenize("Elle")
|
527
|
+
['elle']
|
528
|
+
>>> tokenize("Iel/Iels")
|
529
|
+
['iel', 'iels']
|
530
|
+
>>> tokenize("🦉 Elle")
|
531
|
+
['elle']
|
532
|
+
"""
|
533
|
+
text = text.casefold()
|
534
|
+
text = RE_VALID.sub(" ", text)
|
535
|
+
return text.strip().split()
|