easterobot 1.1.1__tar.gz → 1.3.1__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.
Files changed (101) hide show
  1. {easterobot-1.1.1 → easterobot-1.3.1}/.dockerignore +4 -1
  2. {easterobot-1.1.1 → easterobot-1.3.1}/.github/workflows/tests.yml +13 -0
  3. {easterobot-1.1.1 → easterobot-1.3.1}/Dockerfile +10 -5
  4. {easterobot-1.1.1 → easterobot-1.3.1}/PKG-INFO +10 -1
  5. {easterobot-1.1.1 → easterobot-1.3.1}/README.rst +9 -0
  6. easterobot-1.3.1/conftest.py +44 -0
  7. {easterobot-1.1.1 → easterobot-1.3.1}/docker-compose.yml +1 -1
  8. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/bot.py +15 -6
  9. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/commands/__init__.py +3 -0
  10. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/commands/base.py +4 -1
  11. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/commands/disable.py +2 -1
  12. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/commands/edit.py +2 -1
  13. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/commands/enable.py +2 -1
  14. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/commands/game.py +26 -22
  15. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/commands/help.py +6 -3
  16. easterobot-1.3.1/easterobot/commands/info.py +61 -0
  17. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/commands/reset.py +2 -1
  18. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/commands/search.py +17 -40
  19. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/config.py +72 -2
  20. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/games/connect.py +8 -7
  21. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/games/game.py +105 -34
  22. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/games/rock_paper_scissor.py +22 -8
  23. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/games/tic_tac_toe.py +8 -7
  24. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/hunts/hunt.py +183 -84
  25. easterobot-1.3.1/easterobot/hunts/luck.py +53 -0
  26. easterobot-1.3.1/easterobot/query.py +11 -0
  27. easterobot-1.3.1/easterobot/resources/config.example.yml +443 -0
  28. easterobot-1.3.1/easterobot/utils.py +11 -0
  29. easterobot-1.3.1/entrypoint.sh +6 -0
  30. {easterobot-1.1.1 → easterobot-1.3.1}/pyproject.toml +7 -5
  31. easterobot-1.3.1/tests/constants.py +8 -0
  32. easterobot-1.3.1/tests/test_search.py +49 -0
  33. {easterobot-1.1.1 → easterobot-1.3.1}/tools/chatgpt.txt +2 -2
  34. {easterobot-1.1.1 → easterobot-1.3.1}/uv.lock +27 -1
  35. easterobot-1.1.1/conftest.py +0 -13
  36. easterobot-1.1.1/easterobot/resources/config.example.yml +0 -234
  37. easterobot-1.1.1/entrypoint.sh +0 -6
  38. {easterobot-1.1.1 → easterobot-1.3.1}/.editorconfig +0 -0
  39. {easterobot-1.1.1 → easterobot-1.3.1}/.github/actions/setup-project/action.yml +0 -0
  40. {easterobot-1.1.1 → easterobot-1.3.1}/.github/workflows/docs.yml +0 -0
  41. {easterobot-1.1.1 → easterobot-1.3.1}/.github/workflows/lint.yml +0 -0
  42. {easterobot-1.1.1 → easterobot-1.3.1}/.github/workflows/publish.yml +0 -0
  43. {easterobot-1.1.1 → easterobot-1.3.1}/.gitignore +0 -0
  44. {easterobot-1.1.1 → easterobot-1.3.1}/.pre-commit-config.yaml +0 -0
  45. {easterobot-1.1.1 → easterobot-1.3.1}/.vscode/extensions.json +0 -0
  46. {easterobot-1.1.1 → easterobot-1.3.1}/.vscode/ltex.dictionary.en-US.txt +0 -0
  47. {easterobot-1.1.1 → easterobot-1.3.1}/.vscode/ltex.hiddenFalsePositives.en-US.txt +0 -0
  48. {easterobot-1.1.1 → easterobot-1.3.1}/.vscode/settings.json +0 -0
  49. {easterobot-1.1.1 → easterobot-1.3.1}/LICENSE +0 -0
  50. {easterobot-1.1.1 → easterobot-1.3.1}/docs/conf.py +0 -0
  51. {easterobot-1.1.1 → easterobot-1.3.1}/docs/index.rst +0 -0
  52. {easterobot-1.1.1 → easterobot-1.3.1}/docs/references.rst +0 -0
  53. {easterobot-1.1.1 → easterobot-1.3.1}/docs/resources/favicon.png +0 -0
  54. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/__init__.py +0 -0
  55. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/__main__.py +0 -0
  56. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/alembic/env.py +0 -0
  57. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/alembic/script.py.mako +0 -0
  58. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/alembic/versions/2f0d4305e320_init_database.py +0 -0
  59. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/alembic/versions/940c3b9c702d_add_lock_on_eggs.py +0 -0
  60. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/cli.py +0 -0
  61. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/commands/basket.py +0 -0
  62. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/commands/top.py +0 -0
  63. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/games/__init__.py +0 -0
  64. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/hunts/__init__.py +0 -0
  65. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/hunts/rank.py +0 -0
  66. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/info.py +0 -0
  67. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/logger.py +0 -0
  68. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/models.py +0 -0
  69. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/py.typed +0 -0
  70. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/alembic.ini +0 -0
  71. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/credits.txt +0 -0
  72. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_01.png +0 -0
  73. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_02.png +0 -0
  74. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_03.png +0 -0
  75. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_04.png +0 -0
  76. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_05.png +0 -0
  77. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_06.png +0 -0
  78. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_07.png +0 -0
  79. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_08.png +0 -0
  80. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_09.png +0 -0
  81. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_10.png +0 -0
  82. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_11.png +0 -0
  83. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_12.png +0 -0
  84. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_13.png +0 -0
  85. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_14.png +0 -0
  86. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_15.png +0 -0
  87. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_16.png +0 -0
  88. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_17.png +0 -0
  89. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_18.png +0 -0
  90. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_19.png +0 -0
  91. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/eggs/egg_20.png +0 -0
  92. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/icons/arrow.png +0 -0
  93. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/icons/end.png +0 -0
  94. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/icons/versus.png +0 -0
  95. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/emotes/icons/wait.png +0 -0
  96. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/logging.conf +0 -0
  97. {easterobot-1.1.1 → easterobot-1.3.1}/easterobot/resources/logo.png +0 -0
  98. {easterobot-1.1.1 → easterobot-1.3.1}/tests/__init__.py +0 -0
  99. {easterobot-1.1.1 → easterobot-1.3.1}/tests/test_cli.py +0 -0
  100. {easterobot-1.1.1 → easterobot-1.3.1}/tests/test_config.py +0 -0
  101. {easterobot-1.1.1 → easterobot-1.3.1}/tools/cropping.py +0 -0
@@ -3,7 +3,10 @@
3
3
  !pyproject.toml
4
4
  !LICENSE
5
5
  !/easterobot/*.py
6
- !/easterobot/commands/*.py
6
+ !/easterobot/*/*.py
7
+ !/easterobot/*/*/*.py
8
+ !/easterobot/alembic/script.py.mako
9
+ !/easterobot/py.typed
7
10
  !/easterobot/resources
8
11
  !README.rst
9
12
  !entrypoint.sh
@@ -37,3 +37,16 @@ jobs:
37
37
 
38
38
  - name: Run tests
39
39
  run: uv run poe test
40
+ docker:
41
+ name: Docker build
42
+ runs-on: ubuntu-latest
43
+
44
+ steps:
45
+ - name: Checkout code
46
+ uses: actions/checkout@v4
47
+
48
+ - name: Set up Docker
49
+ uses: docker/setup-buildx-action@v3
50
+
51
+ - name: Build services with Docker Compose
52
+ run: docker compose -f docker-compose.yml build
@@ -2,8 +2,8 @@
2
2
  FROM python:3.12-slim
3
3
 
4
4
  # Metadata for clarity and documentation
5
- LABEL maintainer="your_email@example.com"
6
- LABEL description="Docker image for easterobot using uv for dependency management"
5
+ LABEL maintainer="dashstrom.pro@gmail.com"
6
+ LABEL description="Docker image for easterobot, a discord bot for easter events"
7
7
 
8
8
  # Add the UV binary
9
9
  COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
@@ -17,9 +17,6 @@ RUN mkdir /data
17
17
  # Make it read-only
18
18
  RUN chown easterobot:easterobot /data
19
19
 
20
- # Use non-root user for security
21
- USER easterobot
22
-
23
20
  # Change the working directory to the `src` directory
24
21
  WORKDIR /src
25
22
 
@@ -32,8 +29,16 @@ RUN uv sync --frozen --no-install-project
32
29
  # Copy the project into the image
33
30
  COPY . .
34
31
 
32
+ # Fix permissions
33
+ RUN find /src -type d -exec chmod 755 {} \;
34
+ RUN find /src -type f -exec chmod 644 {} \;
35
+ RUN chmod +x /src/entrypoint.sh
36
+
35
37
  # Sync the project
36
38
  RUN uv sync --frozen
37
39
 
40
+ # Use non-root user for security
41
+ USER easterobot
42
+
38
43
  # Default command (use exec form for signal handling)
39
44
  CMD ["/src/entrypoint.sh"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easterobot
3
- Version: 1.1.1
3
+ Version: 1.3.1
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,6 +121,9 @@ 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 (and for each update)
125
+ chmod -R 700 . && mkdir data -p && chmod 777 data
126
+
124
127
  # Run the docker container
125
128
  docker compose up -d
126
129
 
@@ -130,6 +133,12 @@ from `PyPI <https://pypi.org/project>`_
130
133
  # Remove the container (not the data)
131
134
  docker compose down --rmi all
132
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
+
133
142
  Configuration directory
134
143
  #######################
135
144
 
@@ -83,6 +83,9 @@ 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 (and for each update)
87
+ chmod -R 700 . && mkdir data -p && chmod 777 data
88
+
86
89
  # Run the docker container
87
90
  docker compose up -d
88
91
 
@@ -92,6 +95,12 @@ from `PyPI <https://pypi.org/project>`_
92
95
  # Remove the container (not the data)
93
96
  docker compose down --rmi all
94
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
+
95
104
  Configuration directory
96
105
  #######################
97
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,6 +1,6 @@
1
1
  services:
2
2
  bot:
3
- container_name: easterobot_bot
3
+ container_name: bot
4
4
  stdin_open: true
5
5
  tty: true
6
6
  build:
@@ -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.all()
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.exception("InterruptedCommandError occur")
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,
@@ -9,7 +9,8 @@ from .base import Context, controlled_command, egg_command_group
9
9
 
10
10
 
11
11
  @egg_command_group.command(
12
- name="disable", description="Désactiver la chasse aux œufs dans le salon"
12
+ name="disable",
13
+ description="Désactiver la chasse aux œufs dans le salon",
13
14
  )
14
15
  @controlled_command(cooldown=True, manage_channels=True)
15
16
  async def disable_command(ctx: Context) -> None:
@@ -13,7 +13,8 @@ from .base import Context, controlled_command, egg_command_group
13
13
 
14
14
 
15
15
  @egg_command_group.command(
16
- name="edit", description="Editer le nombre d'œufs d'un membre"
16
+ name="edit",
17
+ description="Editer le nombre d'œufs d'un membre",
17
18
  )
18
19
  @controlled_command(cooldown=True, administrator=True)
19
20
  async def edit_command(
@@ -9,7 +9,8 @@ from .base import Context, controlled_command, egg_command_group
9
9
 
10
10
 
11
11
  @egg_command_group.command(
12
- name="enable", description="Activer la chasse dans le salon"
12
+ name="enable",
13
+ description="Activer la chasse dans le salon",
13
14
  )
14
15
  @controlled_command(
15
16
  cooldown=True,
@@ -130,25 +130,28 @@ async def game_dual( # noqa: C901, D103, PLR0912
130
130
  await session.commit()
131
131
 
132
132
  # Play the game
133
- game = cls(ctx.user, member, msg)
134
- await ctx.client.game.run(game)
135
- winner = await game.wait_winner()
136
-
137
- # Give eggs to the winner or remove previous one
138
- async with lock:
139
- for e in e1:
140
- e.lock = False
141
- if winner:
142
- e.user_id = winner.id
143
- for e in e2:
144
- e.lock = False
145
- if winner:
146
- e.user_id = winner.id
147
- await session.commit()
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
- name="connect4", description="Lancer une partie de puissance 4."
153
+ name="connect4",
154
+ description="Lancer une partie de puissance 4",
152
155
  )
153
156
  @controlled_command(cooldown=True, channel_permissions={"send_messages": True})
154
157
  async def connect4_command(
@@ -161,12 +164,13 @@ async def connect4_command(
161
164
 
162
165
 
163
166
  @egg_command_group.command(
164
- name="tictactoe", description="Lancer une partie de morpion."
167
+ name="tictactoe",
168
+ description="Lancer une partie de morpion",
165
169
  )
166
- @controlled_command(cooldown=True)
170
+ @controlled_command(cooldown=True, channel_permissions={"send_messages": True})
167
171
  async def tictactoe_command(
168
172
  ctx: Context,
169
- member: discord.Member,
173
+ member: Optional[discord.Member] = None,
170
174
  bet: app_commands.Range[int, 0] = 0,
171
175
  ) -> None:
172
176
  """Run a tictactoe."""
@@ -175,12 +179,12 @@ async def tictactoe_command(
175
179
 
176
180
  @egg_command_group.command(
177
181
  name="rockpaperscissor",
178
- description="Lancer une partie de pierre papier ciseaux.",
182
+ description="Lancer une partie de pierre papier ciseaux",
179
183
  )
180
- @controlled_command(cooldown=True)
184
+ @controlled_command(cooldown=True, channel_permissions={"send_messages": True})
181
185
  async def rockpaperscissor_command(
182
186
  ctx: Context,
183
- member: discord.Member,
187
+ member: Optional[discord.Member] = None,
184
188
  bet: app_commands.Range[int, 0] = 0,
185
189
  ) -> None:
186
190
  """Run a rockpaperscissor."""
@@ -7,8 +7,11 @@ from easterobot.hunts.hunt import embed
7
7
  from .base import Context, controlled_command, egg_command_group
8
8
 
9
9
 
10
- @egg_command_group.command(name="help", description="Obtenir de l'aide")
11
- @controlled_command(cooldown=True)
10
+ @egg_command_group.command(
11
+ name="help",
12
+ description="Obtenir l'aide des commandes",
13
+ )
14
+ @controlled_command(cooldown=True, channel_permissions={"send_messages": True})
12
15
  async def help_command(ctx: Context) -> None:
13
16
  """Help command."""
14
17
  emb = embed(
@@ -30,4 +33,4 @@ async def help_command(ctx: Context) -> None:
30
33
  value=f"{option.description}",
31
34
  inline=False,
32
35
  )
33
- await ctx.response.send_message(embed=emb, ephemeral=True)
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'oeufs : `{hunter_rank.eggs}`\n"
55
+ f"Chance brute : `{member_luck.luck:.0%}`\n"
56
+ f"Chance de trouver un oeuf : `{member_luck.discovered:.0%}`\n"
57
+ f"Chance de se faire voler : `{member_luck.spotted:.0%}`"
58
+ ),
59
+ ),
60
+ ephemeral=True,
61
+ )
@@ -14,7 +14,8 @@ from .base import Context, Interaction, controlled_command, egg_command_group
14
14
 
15
15
 
16
16
  @egg_command_group.command(
17
- name="reset", description="Réinitialiser la chasse aux œufs"
17
+ name="reset",
18
+ description="Réinitialiser la chasse aux œufs",
18
19
  )
19
20
  @controlled_command(cooldown=True, administrator=True)
20
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 and_, func, select
7
+ from sqlalchemy import select
8
8
  from sqlalchemy.ext.asyncio import AsyncSession
9
9
 
10
- from easterobot.config import RAND, agree
10
+ from easterobot.config import agree
11
11
  from easterobot.hunts.hunt import embed
12
12
  from easterobot.models import Egg, Hunt
13
13
 
@@ -21,7 +21,10 @@ from .base import (
21
21
  logger = logging.getLogger("easterobot")
22
22
 
23
23
 
24
- @egg_command_group.command(name="search", description="Rechercher un œuf")
24
+ @egg_command_group.command(
25
+ name="search",
26
+ description="Rechercher un œuf",
27
+ )
25
28
  @controlled_command(cooldown=True, channel_permissions={"send_messages": True})
26
29
  async def search_command(ctx: Context) -> None:
27
30
  """Search command."""
@@ -34,7 +37,7 @@ async def search_command(ctx: Context) -> None:
34
37
  "La chasse aux œufs n'est pas activée dans ce salon",
35
38
  ephemeral=True,
36
39
  )
37
- return
40
+ raise InterruptedCommandError
38
41
  try:
39
42
  await ctx.response.defer(ephemeral=False)
40
43
  except discord.errors.NotFound as err:
@@ -42,39 +45,15 @@ async def search_command(ctx: Context) -> None:
42
45
  name = ctx.user.display_name
43
46
 
44
47
  async with AsyncSession(ctx.client.engine) as session:
45
- egg_max = await session.scalar(
46
- select(
47
- func.count().label("max"),
48
- )
49
- .where(Egg.guild_id == ctx.guild_id)
50
- .group_by(Egg.user_id)
51
- .order_by(func.count().label("max").desc())
52
- .limit(1)
53
- )
54
- egg_max = egg_max or 0
55
- egg_count = await session.scalar(
56
- select(func.count().label("count")).where(
57
- and_(
58
- Egg.guild_id == ctx.guild.id,
59
- Egg.user_id == ctx.user.id,
60
- )
61
- )
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(),
62
53
  )
63
- if egg_count is None:
64
- egg_count = 0
65
- ratio = egg_count / egg_max if egg_max != 0 else 1.0
66
-
67
- discovered = ctx.client.config.commands.search.discovered
68
- prob_d = (discovered.max - discovered.min) * (1 - ratio) + discovered.min
69
54
 
70
- sample_d = RAND.random()
71
- if prob_d > sample_d or egg_count < discovered.shield:
72
- sample_s = RAND.random()
73
- spotted = ctx.client.config.commands.search.spotted
74
- prob_s = (spotted.max - spotted.min) * ratio + spotted.min
75
- logger.info("discovered: %.2f > %.2f", prob_d, sample_d)
76
- if prob_s > sample_s and egg_count > spotted.shield:
77
- logger.info("spotted: %.2f > %.2f", prob_s, sample_s)
55
+ if luck.sample_discovered():
56
+ if luck.sample_spotted():
78
57
 
79
58
  async def send_method(
80
59
  *args: Any, **kwargs: Any
@@ -88,7 +67,6 @@ async def search_command(ctx: Context) -> None:
88
67
  send_method=send_method,
89
68
  )
90
69
  else:
91
- logger.info("found: %.2f > %.2f", prob_s, sample_s)
92
70
  emoji = ctx.client.egg_emotes.rand()
93
71
  async with AsyncSession(ctx.client.engine) as session:
94
72
  session.add(
@@ -105,7 +83,7 @@ async def search_command(ctx: Context) -> None:
105
83
  "%s (%s) got an egg for a total %s in %s",
106
84
  ctx.user,
107
85
  ctx.user.id,
108
- agree("{0} egg", "{0} eggs", egg_count),
86
+ agree("{0} egg", "{0} eggs", luck.egg_count),
109
87
  ctx.channel.jump_url,
110
88
  )
111
89
  await ctx.followup.send(
@@ -113,15 +91,14 @@ async def search_command(ctx: Context) -> None:
113
91
  title=f"{name} récupère un œuf",
114
92
  description=ctx.client.config.hidden(ctx.user),
115
93
  thumbnail=emoji.url,
116
- egg_count=egg_count + 1,
94
+ egg_count=luck.egg_count + 1,
117
95
  )
118
96
  )
119
97
  else:
120
- logger.info("failed: %.2f > %.2f", prob_d, sample_d)
121
98
  await ctx.followup.send(
122
99
  embed=embed(
123
100
  title=f"{name} repart bredouille",
124
101
  description=ctx.client.config.failed(ctx.user),
125
- egg_count=egg_count,
102
+ egg_count=luck.egg_count,
126
103
  )
127
104
  )