easterobot 1.0.0__py3-none-any.whl
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/__init__.py +19 -0
- easterobot/__main__.py +6 -0
- easterobot/bot.py +584 -0
- easterobot/cli.py +127 -0
- easterobot/commands/__init__.py +28 -0
- easterobot/commands/base.py +171 -0
- easterobot/commands/basket.py +99 -0
- easterobot/commands/disable.py +29 -0
- easterobot/commands/edit.py +68 -0
- easterobot/commands/enable.py +35 -0
- easterobot/commands/help.py +33 -0
- easterobot/commands/reset.py +121 -0
- easterobot/commands/search.py +127 -0
- easterobot/commands/top.py +105 -0
- easterobot/config.py +401 -0
- easterobot/info.py +18 -0
- easterobot/logger.py +16 -0
- easterobot/models.py +58 -0
- easterobot/py.typed +1 -0
- easterobot/resources/config.example.yml +226 -0
- easterobot/resources/credits.txt +1 -0
- easterobot/resources/eggs/egg_01.png +0 -0
- easterobot/resources/eggs/egg_02.png +0 -0
- easterobot/resources/eggs/egg_03.png +0 -0
- easterobot/resources/eggs/egg_04.png +0 -0
- easterobot/resources/eggs/egg_05.png +0 -0
- easterobot/resources/eggs/egg_06.png +0 -0
- easterobot/resources/eggs/egg_07.png +0 -0
- easterobot/resources/eggs/egg_08.png +0 -0
- easterobot/resources/eggs/egg_09.png +0 -0
- easterobot/resources/eggs/egg_10.png +0 -0
- easterobot/resources/eggs/egg_11.png +0 -0
- easterobot/resources/eggs/egg_12.png +0 -0
- easterobot/resources/eggs/egg_13.png +0 -0
- easterobot/resources/eggs/egg_14.png +0 -0
- easterobot/resources/eggs/egg_15.png +0 -0
- easterobot/resources/eggs/egg_16.png +0 -0
- easterobot/resources/eggs/egg_17.png +0 -0
- easterobot/resources/eggs/egg_18.png +0 -0
- easterobot/resources/eggs/egg_19.png +0 -0
- easterobot/resources/eggs/egg_20.png +0 -0
- easterobot/resources/logging.conf +47 -0
- easterobot/resources/logo.png +0 -0
- easterobot-1.0.0.dist-info/METADATA +242 -0
- easterobot-1.0.0.dist-info/RECORD +48 -0
- easterobot-1.0.0.dist-info/WHEEL +4 -0
- easterobot-1.0.0.dist-info/entry_points.txt +2 -0
- easterobot-1.0.0.dist-info/licenses/LICENSE +21 -0
easterobot/cli.py
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
"""Module for command line interface."""
|
2
|
+
|
3
|
+
import argparse
|
4
|
+
import logging
|
5
|
+
import sys
|
6
|
+
from collections.abc import Sequence
|
7
|
+
from typing import NoReturn, Optional
|
8
|
+
|
9
|
+
from .bot import DEFAULT_CONFIG_PATH, Easterobot
|
10
|
+
from .info import __issues__, __summary__, __version__
|
11
|
+
|
12
|
+
LOG_LEVELS = ["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
|
13
|
+
logger = logging.getLogger(__name__)
|
14
|
+
|
15
|
+
|
16
|
+
class HelpArgumentParser(argparse.ArgumentParser):
|
17
|
+
def error(self, message: str) -> NoReturn: # pragma: no cover
|
18
|
+
"""Handle error from argparse.ArgumentParser."""
|
19
|
+
self.print_help(sys.stderr)
|
20
|
+
self.exit(2, f"{self.prog}: error: {message}\n")
|
21
|
+
|
22
|
+
|
23
|
+
def get_parser() -> argparse.ArgumentParser:
|
24
|
+
"""Prepare ArgumentParser."""
|
25
|
+
parser = HelpArgumentParser(
|
26
|
+
prog="easterobot",
|
27
|
+
description=__summary__,
|
28
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
29
|
+
)
|
30
|
+
parser.add_argument(
|
31
|
+
"--version",
|
32
|
+
action="version",
|
33
|
+
version=f"%(prog)s, version {__version__}",
|
34
|
+
)
|
35
|
+
|
36
|
+
# Add subparsers
|
37
|
+
subparsers = parser.add_subparsers(
|
38
|
+
help="desired action to perform",
|
39
|
+
dest="action",
|
40
|
+
required=True,
|
41
|
+
)
|
42
|
+
|
43
|
+
# Add parent parser with common arguments
|
44
|
+
parent_parser = HelpArgumentParser(add_help=False)
|
45
|
+
parent_parser.add_argument(
|
46
|
+
"-v",
|
47
|
+
"--verbose",
|
48
|
+
help="verbose mode, enable DEBUG messages.",
|
49
|
+
action="store_true",
|
50
|
+
required=False,
|
51
|
+
)
|
52
|
+
parent_parser.add_argument(
|
53
|
+
"-t",
|
54
|
+
"--token",
|
55
|
+
help="run using a token and the default configuration.",
|
56
|
+
)
|
57
|
+
parent_parser.add_argument(
|
58
|
+
"-e",
|
59
|
+
"--env",
|
60
|
+
help="load token from DISCORD_TOKEN environnement variable.",
|
61
|
+
action="store_true",
|
62
|
+
)
|
63
|
+
|
64
|
+
# Parser of hello command
|
65
|
+
run_parser = subparsers.add_parser(
|
66
|
+
"run",
|
67
|
+
parents=[parent_parser],
|
68
|
+
help="start the bot.",
|
69
|
+
)
|
70
|
+
run_parser.add_argument(
|
71
|
+
"-c",
|
72
|
+
"--config",
|
73
|
+
help=f"path to configuration, default to {DEFAULT_CONFIG_PATH}.",
|
74
|
+
default=DEFAULT_CONFIG_PATH,
|
75
|
+
)
|
76
|
+
|
77
|
+
# Parser of hello command
|
78
|
+
run_parser = subparsers.add_parser(
|
79
|
+
"generate",
|
80
|
+
parents=[parent_parser],
|
81
|
+
help="generate a configuration.",
|
82
|
+
)
|
83
|
+
run_parser.add_argument(
|
84
|
+
"-i",
|
85
|
+
"--interactive",
|
86
|
+
help="ask questions for create a ready to use config.",
|
87
|
+
action="store_true",
|
88
|
+
)
|
89
|
+
run_parser.add_argument("destination", default=".")
|
90
|
+
return parser
|
91
|
+
|
92
|
+
|
93
|
+
def setup_logging(verbose: Optional[bool] = None) -> None:
|
94
|
+
"""Do setup logging."""
|
95
|
+
# Setup logging
|
96
|
+
logging.basicConfig(
|
97
|
+
level=logging.DEBUG if verbose else logging.WARNING,
|
98
|
+
format="[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s",
|
99
|
+
)
|
100
|
+
|
101
|
+
|
102
|
+
def entrypoint(argv: Optional[Sequence[str]] = None) -> None:
|
103
|
+
"""Entrypoint for command line interface."""
|
104
|
+
try:
|
105
|
+
parser = get_parser()
|
106
|
+
args = parser.parse_args(argv)
|
107
|
+
setup_logging(args.verbose)
|
108
|
+
if args.action == "run":
|
109
|
+
bot = Easterobot.from_config(
|
110
|
+
args.config,
|
111
|
+
token=args.token,
|
112
|
+
env=args.env,
|
113
|
+
)
|
114
|
+
bot.auto_run()
|
115
|
+
elif args.action == "generate":
|
116
|
+
Easterobot.generate(
|
117
|
+
destination=args.destination,
|
118
|
+
token=args.token,
|
119
|
+
env=args.env,
|
120
|
+
interactive=args.interactive,
|
121
|
+
)
|
122
|
+
else:
|
123
|
+
parser.error("No command specified") # pragma: no cover
|
124
|
+
except Exception as err: # NoQA: BLE001 # pragma: no cover
|
125
|
+
logger.critical("Unexpected error", exc_info=err)
|
126
|
+
logger.critical("Please, report this error to %s.", __issues__)
|
127
|
+
sys.exit(1)
|
@@ -0,0 +1,28 @@
|
|
1
|
+
"""Init package."""
|
2
|
+
|
3
|
+
from easterobot.bot import Easterobot
|
4
|
+
from easterobot.commands.base import egg_command_group
|
5
|
+
from easterobot.commands.basket import basket_command
|
6
|
+
from easterobot.commands.disable import disable_command
|
7
|
+
from easterobot.commands.edit import edit_command
|
8
|
+
from easterobot.commands.enable import enable_command
|
9
|
+
from easterobot.commands.help import help_command
|
10
|
+
from easterobot.commands.reset import reset_command
|
11
|
+
from easterobot.commands.search import search_command
|
12
|
+
from easterobot.commands.top import top_command
|
13
|
+
|
14
|
+
__all__ = [
|
15
|
+
"basket_command",
|
16
|
+
"disable_command",
|
17
|
+
"edit_command",
|
18
|
+
"egg_command_group",
|
19
|
+
"enable_command",
|
20
|
+
"help_command",
|
21
|
+
"reset_command",
|
22
|
+
"search_command",
|
23
|
+
"top_command",
|
24
|
+
]
|
25
|
+
|
26
|
+
|
27
|
+
async def setup(bot: Easterobot) -> None:
|
28
|
+
bot.tree.add_command(egg_command_group)
|
@@ -0,0 +1,171 @@
|
|
1
|
+
"""Base module for command."""
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
import functools
|
5
|
+
import logging
|
6
|
+
from collections.abc import Coroutine
|
7
|
+
from time import time
|
8
|
+
from typing import Any, Callable, TypeVar, Union, cast
|
9
|
+
|
10
|
+
import discord
|
11
|
+
from discord import app_commands
|
12
|
+
from sqlalchemy import and_, delete
|
13
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
14
|
+
from typing_extensions import Concatenate, ParamSpec
|
15
|
+
|
16
|
+
from easterobot.bot import Easterobot
|
17
|
+
from easterobot.models import Cooldown
|
18
|
+
|
19
|
+
egg_command_group = app_commands.Group(
|
20
|
+
name="egg",
|
21
|
+
description="Commandes en lien avec Pâque",
|
22
|
+
guild_only=True,
|
23
|
+
)
|
24
|
+
logger = logging.getLogger(__name__)
|
25
|
+
cooldown_lock = asyncio.Lock()
|
26
|
+
|
27
|
+
|
28
|
+
class InterruptedCommandError(Exception):
|
29
|
+
pass
|
30
|
+
|
31
|
+
|
32
|
+
InteractionChannel = Union[
|
33
|
+
discord.VoiceChannel,
|
34
|
+
discord.StageChannel,
|
35
|
+
discord.TextChannel,
|
36
|
+
discord.ForumChannel,
|
37
|
+
discord.CategoryChannel,
|
38
|
+
discord.Thread,
|
39
|
+
discord.DMChannel,
|
40
|
+
discord.GroupChannel,
|
41
|
+
]
|
42
|
+
|
43
|
+
|
44
|
+
class Context(discord.Interaction[Easterobot]):
|
45
|
+
user: discord.Member
|
46
|
+
command: "app_commands.Command[Any, ..., Any]" # type: ignore[assignment]
|
47
|
+
channel: InteractionChannel
|
48
|
+
channel_id: int
|
49
|
+
guild: discord.Guild
|
50
|
+
guild_id: int
|
51
|
+
|
52
|
+
|
53
|
+
P = ParamSpec("P")
|
54
|
+
T = TypeVar("T")
|
55
|
+
Interaction = discord.Interaction[Easterobot]
|
56
|
+
Coro = Coroutine[Any, Any, T]
|
57
|
+
|
58
|
+
|
59
|
+
def controlled_command(
|
60
|
+
*, cooldown: bool = True, **perms: bool
|
61
|
+
) -> Callable[
|
62
|
+
[Callable[Concatenate[Context, P], Coro[None]]],
|
63
|
+
Callable[Concatenate[Interaction, P], Coro[None]],
|
64
|
+
]:
|
65
|
+
"""Add a cooldown and permission check."""
|
66
|
+
|
67
|
+
def decorator(
|
68
|
+
f: Callable[Concatenate[Context, P], Coro[None]],
|
69
|
+
) -> Callable[Concatenate[Interaction, P], Coro[None]]:
|
70
|
+
@functools.wraps(f)
|
71
|
+
async def decorated(
|
72
|
+
interaction: Interaction, *args: P.args, **kwargs: P.kwargs
|
73
|
+
) -> None:
|
74
|
+
# Check if interaction is valid
|
75
|
+
if not isinstance(interaction.command, app_commands.Command):
|
76
|
+
logger.warning("No command provided %s", interaction)
|
77
|
+
return
|
78
|
+
if interaction.channel is None or interaction.channel_id is None:
|
79
|
+
logger.warning("No channel provided %s", interaction)
|
80
|
+
return
|
81
|
+
|
82
|
+
# Preformat event repr
|
83
|
+
event_repr = (
|
84
|
+
f"/{interaction.command.qualified_name} by {interaction.user} "
|
85
|
+
f"({interaction.user.id}) in {interaction.channel.jump_url}"
|
86
|
+
)
|
87
|
+
|
88
|
+
# Check if in guild
|
89
|
+
if interaction.guild is None or interaction.guild_id is None:
|
90
|
+
logger.warning("Must be use in a guild !")
|
91
|
+
return
|
92
|
+
if not isinstance(interaction.user, discord.Member):
|
93
|
+
logger.warning("No channel provided %s", interaction)
|
94
|
+
return
|
95
|
+
|
96
|
+
# Compute needed permissions
|
97
|
+
needed_perms = discord.Permissions(**perms)
|
98
|
+
have_perms = interaction.user.guild_permissions.is_superset(
|
99
|
+
needed_perms
|
100
|
+
)
|
101
|
+
admin_ids = interaction.client.config.admins
|
102
|
+
is_super_admin = (
|
103
|
+
interaction.user.id in admin_ids
|
104
|
+
or interaction.user.id == interaction.client.owner_id
|
105
|
+
)
|
106
|
+
if not have_perms and not is_super_admin:
|
107
|
+
logger.warning("%s failed for wrong permissions", event_repr)
|
108
|
+
await interaction.response.send_message(
|
109
|
+
"Vous n'avez pas la permission",
|
110
|
+
ephemeral=True,
|
111
|
+
)
|
112
|
+
return
|
113
|
+
|
114
|
+
# Check command cooldown
|
115
|
+
cmd = interaction.command.name
|
116
|
+
if cooldown:
|
117
|
+
available_at = None
|
118
|
+
async with (
|
119
|
+
AsyncSession(interaction.client.engine) as session,
|
120
|
+
cooldown_lock, # We must use lock for avoid race condition
|
121
|
+
):
|
122
|
+
cd_user = await session.get(
|
123
|
+
Cooldown,
|
124
|
+
(interaction.user.id, interaction.guild_id, cmd),
|
125
|
+
)
|
126
|
+
cd_cmd = interaction.client.config.commands[cmd].cooldown
|
127
|
+
now = time()
|
128
|
+
if cd_user is None or now > cd_cmd + cd_user.timestamp:
|
129
|
+
await session.merge(
|
130
|
+
Cooldown(
|
131
|
+
user_id=interaction.user.id,
|
132
|
+
guild_id=interaction.guild_id,
|
133
|
+
command=cmd,
|
134
|
+
timestamp=now,
|
135
|
+
)
|
136
|
+
)
|
137
|
+
await session.commit()
|
138
|
+
else:
|
139
|
+
available_at = cd_cmd + cd_user.timestamp
|
140
|
+
if available_at:
|
141
|
+
logger.warning("%s failed for cooldown", event_repr)
|
142
|
+
await interaction.response.send_message(
|
143
|
+
"Vous devez encore attendre "
|
144
|
+
f"<t:{available_at + 1:.0f}:R>",
|
145
|
+
ephemeral=True,
|
146
|
+
)
|
147
|
+
return
|
148
|
+
logger.info("%s", event_repr)
|
149
|
+
try:
|
150
|
+
await f(cast(Context, interaction), *args, **kwargs)
|
151
|
+
except InterruptedCommandError:
|
152
|
+
logger.exception("InterruptedCommandError occur")
|
153
|
+
async with (
|
154
|
+
AsyncSession(interaction.client.engine) as session,
|
155
|
+
cooldown_lock,
|
156
|
+
):
|
157
|
+
# This is unsafe
|
158
|
+
await session.execute(
|
159
|
+
delete(Cooldown).where(
|
160
|
+
and_(
|
161
|
+
Cooldown.guild_id == interaction.guild_id,
|
162
|
+
Cooldown.user_id == interaction.user.id,
|
163
|
+
Cooldown.command == cmd,
|
164
|
+
)
|
165
|
+
)
|
166
|
+
)
|
167
|
+
await session.commit()
|
168
|
+
|
169
|
+
return decorated
|
170
|
+
|
171
|
+
return decorator
|
@@ -0,0 +1,99 @@
|
|
1
|
+
"""Command basket."""
|
2
|
+
|
3
|
+
from typing import Optional
|
4
|
+
|
5
|
+
import discord
|
6
|
+
from discord import app_commands
|
7
|
+
from sqlalchemy import and_, func, select
|
8
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
9
|
+
|
10
|
+
from easterobot.bot import embed
|
11
|
+
from easterobot.commands.base import (
|
12
|
+
Context,
|
13
|
+
controlled_command,
|
14
|
+
egg_command_group,
|
15
|
+
)
|
16
|
+
from easterobot.config import agree
|
17
|
+
from easterobot.models import Egg
|
18
|
+
|
19
|
+
|
20
|
+
@egg_command_group.command(
|
21
|
+
name="basket",
|
22
|
+
description="Regarder le contenu d'un panier",
|
23
|
+
)
|
24
|
+
@app_commands.describe(
|
25
|
+
user="Membre possèdant le panier à inspecter",
|
26
|
+
)
|
27
|
+
@controlled_command(cooldown=True)
|
28
|
+
async def basket_command(
|
29
|
+
ctx: Context, user: Optional[discord.Member] = None
|
30
|
+
) -> None:
|
31
|
+
"""Show current user basket."""
|
32
|
+
# Delay the response
|
33
|
+
await ctx.response.defer(ephemeral=True)
|
34
|
+
|
35
|
+
# Set the user of the basket
|
36
|
+
hunter = user or ctx.user
|
37
|
+
|
38
|
+
# Util for accord in some language
|
39
|
+
you = ctx.user == hunter
|
40
|
+
|
41
|
+
async with AsyncSession(ctx.client.engine) as session:
|
42
|
+
morsels = []
|
43
|
+
missing = []
|
44
|
+
user_egg_count = await ctx.client.get_rank(
|
45
|
+
session, ctx.guild_id, hunter.id
|
46
|
+
)
|
47
|
+
rank = user_egg_count[1] if user_egg_count else None
|
48
|
+
res = await session.execute(
|
49
|
+
select(
|
50
|
+
Egg.emoji_id,
|
51
|
+
func.count().label("count"),
|
52
|
+
)
|
53
|
+
.where(
|
54
|
+
and_(
|
55
|
+
Egg.guild_id == ctx.guild.id,
|
56
|
+
Egg.user_id == hunter.id,
|
57
|
+
)
|
58
|
+
)
|
59
|
+
.group_by(Egg.emoji_id)
|
60
|
+
)
|
61
|
+
egg_counts: dict[int, int] = dict(res.all()) # type: ignore[arg-type]
|
62
|
+
egg_count = 0
|
63
|
+
for emoji in ctx.client.app_emojis.choices:
|
64
|
+
try:
|
65
|
+
type_count = egg_counts.pop(emoji.id)
|
66
|
+
egg_count += type_count
|
67
|
+
morsels.append(f"{emoji} \xd7 {type_count}")
|
68
|
+
except KeyError: # noqa: PERF203
|
69
|
+
missing.append(emoji)
|
70
|
+
|
71
|
+
absent_count = sum(egg_counts.values())
|
72
|
+
if absent_count:
|
73
|
+
egg_count += absent_count
|
74
|
+
morsels.insert(0, f"🥚 \xd7 {absent_count}")
|
75
|
+
|
76
|
+
if rank is not None:
|
77
|
+
il = ctx.client.config.conjugate("{Iel} est", hunter)
|
78
|
+
morsels.insert(
|
79
|
+
0,
|
80
|
+
f"**{'Tu es' if you else il} au rang** {rank}\n",
|
81
|
+
)
|
82
|
+
|
83
|
+
their = "te" if you else "lui"
|
84
|
+
if missing:
|
85
|
+
morsels.append(
|
86
|
+
f"\nIl {their} manque : {''.join(map(str, missing))}"
|
87
|
+
)
|
88
|
+
text = "\n".join(morsels).strip()
|
89
|
+
await ctx.followup.send(
|
90
|
+
embed=embed(
|
91
|
+
title=f"Contenu du panier de {hunter.display_name}",
|
92
|
+
description=text,
|
93
|
+
footer=(
|
94
|
+
f"Cela {their} fait un total de "
|
95
|
+
+ agree("{0} œuf", "{0} œufs", egg_count)
|
96
|
+
),
|
97
|
+
),
|
98
|
+
ephemeral=True,
|
99
|
+
)
|
@@ -0,0 +1,29 @@
|
|
1
|
+
"""Module for disable hunt."""
|
2
|
+
|
3
|
+
from sqlalchemy import delete, select
|
4
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
5
|
+
|
6
|
+
from easterobot.models import Hunt
|
7
|
+
|
8
|
+
from .base import Context, controlled_command, egg_command_group
|
9
|
+
|
10
|
+
|
11
|
+
@egg_command_group.command(
|
12
|
+
name="disable", description="Désactiver la chasse aux œufs dans le salon"
|
13
|
+
)
|
14
|
+
@controlled_command(cooldown=True, manage_channels=True)
|
15
|
+
async def disable_command(ctx: Context) -> None:
|
16
|
+
"""Disable the hunt."""
|
17
|
+
await ctx.response.defer(ephemeral=True)
|
18
|
+
async with AsyncSession(ctx.client.engine) as session:
|
19
|
+
old = await session.scalar(
|
20
|
+
select(Hunt).where(Hunt.channel_id == ctx.channel.id)
|
21
|
+
)
|
22
|
+
if old:
|
23
|
+
await session.execute(
|
24
|
+
delete(Hunt).where(Hunt.channel_id == ctx.channel.id)
|
25
|
+
)
|
26
|
+
await session.commit()
|
27
|
+
await ctx.followup.send(
|
28
|
+
f"Chasse aux œufs{'' if old else ' déjà'} désactivée", ephemeral=True
|
29
|
+
)
|
@@ -0,0 +1,68 @@
|
|
1
|
+
"""Module for edit command."""
|
2
|
+
|
3
|
+
import discord
|
4
|
+
from sqlalchemy import and_, delete, select
|
5
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
6
|
+
|
7
|
+
from easterobot.bot import embed
|
8
|
+
from easterobot.config import RAND, agree
|
9
|
+
from easterobot.models import Egg
|
10
|
+
|
11
|
+
from .base import Context, controlled_command, egg_command_group
|
12
|
+
|
13
|
+
|
14
|
+
@egg_command_group.command(
|
15
|
+
name="edit", description="Editer le nombre d'œufs d'un membre"
|
16
|
+
)
|
17
|
+
@controlled_command(cooldown=True, administrator=True)
|
18
|
+
async def edit_command(
|
19
|
+
ctx: Context,
|
20
|
+
user: discord.Member,
|
21
|
+
oeufs: int,
|
22
|
+
) -> None:
|
23
|
+
"""Edit command."""
|
24
|
+
oeufs = min(max(oeufs, 0), 100_000)
|
25
|
+
await ctx.response.defer(ephemeral=True)
|
26
|
+
async with AsyncSession(ctx.client.engine) as session:
|
27
|
+
eggs: list[Egg] = list(
|
28
|
+
(
|
29
|
+
await session.scalars(
|
30
|
+
select(Egg).where(
|
31
|
+
and_(
|
32
|
+
Egg.guild_id == ctx.guild_id,
|
33
|
+
Egg.user_id == user.id,
|
34
|
+
)
|
35
|
+
)
|
36
|
+
)
|
37
|
+
).all()
|
38
|
+
)
|
39
|
+
diff = len(eggs) - oeufs
|
40
|
+
if diff > 0:
|
41
|
+
to_delete = []
|
42
|
+
for _ in range(diff):
|
43
|
+
egg = RAND.choice(eggs)
|
44
|
+
eggs.remove(egg)
|
45
|
+
to_delete.append(egg.id)
|
46
|
+
await session.execute(delete(Egg).where(Egg.id.in_(to_delete)))
|
47
|
+
await session.commit()
|
48
|
+
elif diff < 0:
|
49
|
+
for _ in range(-diff):
|
50
|
+
session.add(
|
51
|
+
Egg(
|
52
|
+
guild_id=ctx.guild_id,
|
53
|
+
channel_id=ctx.channel_id,
|
54
|
+
user_id=user.id,
|
55
|
+
emoji_id=ctx.client.app_emojis.rand().id,
|
56
|
+
)
|
57
|
+
)
|
58
|
+
await session.commit()
|
59
|
+
await ctx.followup.send(
|
60
|
+
embed=embed(
|
61
|
+
title="Edition terminée",
|
62
|
+
description=(
|
63
|
+
f"{user.mention} a maintenant "
|
64
|
+
f"{agree('{0} œuf', '{0} œufs', oeufs)}"
|
65
|
+
),
|
66
|
+
),
|
67
|
+
ephemeral=True,
|
68
|
+
)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
"""Command enable."""
|
2
|
+
|
3
|
+
from sqlalchemy import select
|
4
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
5
|
+
|
6
|
+
from easterobot.models import Hunt
|
7
|
+
|
8
|
+
from .base import Context, controlled_command, egg_command_group
|
9
|
+
|
10
|
+
|
11
|
+
@egg_command_group.command(
|
12
|
+
name="enable", description="Activer la chasse dans le salon"
|
13
|
+
)
|
14
|
+
@controlled_command(cooldown=True, manage_channels=True)
|
15
|
+
async def enable_command(
|
16
|
+
ctx: Context,
|
17
|
+
) -> None:
|
18
|
+
"""Enable hunt in a channel."""
|
19
|
+
await ctx.response.defer(ephemeral=True)
|
20
|
+
async with AsyncSession(ctx.client.engine) as session:
|
21
|
+
old = await session.scalar(
|
22
|
+
select(Hunt).where(Hunt.channel_id == ctx.channel.id)
|
23
|
+
)
|
24
|
+
if not old:
|
25
|
+
session.add(
|
26
|
+
Hunt(
|
27
|
+
channel_id=ctx.channel.id,
|
28
|
+
guild_id=ctx.guild_id,
|
29
|
+
next_egg=0,
|
30
|
+
)
|
31
|
+
)
|
32
|
+
await session.commit()
|
33
|
+
await ctx.followup.send(
|
34
|
+
f"Chasse aux œufs{' déjà' if old else ''} activée", ephemeral=True
|
35
|
+
)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
"""Module for help command."""
|
2
|
+
|
3
|
+
from discord.app_commands import AppCommandGroup
|
4
|
+
|
5
|
+
from easterobot.bot import embed
|
6
|
+
|
7
|
+
from .base import Context, controlled_command, egg_command_group
|
8
|
+
|
9
|
+
|
10
|
+
@egg_command_group.command(name="help", description="Obtenir de l'aide")
|
11
|
+
@controlled_command(cooldown=True)
|
12
|
+
async def help_command(ctx: Context) -> None:
|
13
|
+
"""Help command."""
|
14
|
+
emb = embed(
|
15
|
+
title="Liste des commandes",
|
16
|
+
description=ctx.client.description,
|
17
|
+
thumbnail=(
|
18
|
+
ctx.client.user.display_avatar.url if ctx.client.user else None
|
19
|
+
),
|
20
|
+
footer="Crée par dashstrom",
|
21
|
+
)
|
22
|
+
for command in ctx.client.app_commands:
|
23
|
+
for option in command.options:
|
24
|
+
if not isinstance(option, AppCommandGroup):
|
25
|
+
continue
|
26
|
+
emb.add_field(
|
27
|
+
name=(
|
28
|
+
f"</{egg_command_group.name} {option.name}:{command.id}>"
|
29
|
+
),
|
30
|
+
value=f"{option.description}",
|
31
|
+
inline=False,
|
32
|
+
)
|
33
|
+
await ctx.response.send_message(embed=emb, ephemeral=True)
|