easterobot 1.1.1__py3-none-any.whl → 1.3.1__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/bot.py +15 -6
- easterobot/commands/__init__.py +3 -0
- easterobot/commands/base.py +4 -1
- easterobot/commands/disable.py +2 -1
- easterobot/commands/edit.py +2 -1
- easterobot/commands/enable.py +2 -1
- easterobot/commands/game.py +26 -22
- easterobot/commands/help.py +6 -3
- easterobot/commands/info.py +61 -0
- easterobot/commands/reset.py +2 -1
- easterobot/commands/search.py +17 -40
- easterobot/config.py +72 -2
- easterobot/games/connect.py +8 -7
- easterobot/games/game.py +105 -34
- easterobot/games/rock_paper_scissor.py +22 -8
- easterobot/games/tic_tac_toe.py +8 -7
- easterobot/hunts/hunt.py +183 -84
- easterobot/hunts/luck.py +53 -0
- easterobot/query.py +11 -0
- easterobot/resources/config.example.yml +220 -11
- easterobot/utils.py +11 -0
- {easterobot-1.1.1.dist-info → easterobot-1.3.1.dist-info}/METADATA +10 -1
- {easterobot-1.1.1.dist-info → easterobot-1.3.1.dist-info}/RECORD +26 -22
- {easterobot-1.1.1.dist-info → easterobot-1.3.1.dist-info}/WHEEL +0 -0
- {easterobot-1.1.1.dist-info → easterobot-1.3.1.dist-info}/entry_points.txt +0 -0
- {easterobot-1.1.1.dist-info → easterobot-1.3.1.dist-info}/licenses/LICENSE +0 -0
easterobot/bot.py
CHANGED
@@ -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()
|
easterobot/commands/__init__.py
CHANGED
@@ -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)
|
easterobot/commands/base.py
CHANGED
@@ -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,
|
easterobot/commands/disable.py
CHANGED
@@ -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",
|
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:
|
easterobot/commands/edit.py
CHANGED
@@ -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",
|
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(
|
easterobot/commands/enable.py
CHANGED
@@ -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",
|
12
|
+
name="enable",
|
13
|
+
description="Activer la chasse dans le salon",
|
13
14
|
)
|
14
15
|
@controlled_command(
|
15
16
|
cooldown=True,
|
easterobot/commands/game.py
CHANGED
@@ -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
|
-
|
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
|
-
name="connect4",
|
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",
|
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."""
|
easterobot/commands/help.py
CHANGED
@@ -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(
|
11
|
-
|
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
|
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
|
+
)
|
easterobot/commands/reset.py
CHANGED
@@ -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",
|
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:
|
easterobot/commands/search.py
CHANGED
@@ -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
|
|
@@ -21,7 +21,10 @@ from .base import (
|
|
21
21
|
logger = logging.getLogger("easterobot")
|
22
22
|
|
23
23
|
|
24
|
-
@egg_command_group.command(
|
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
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
.
|
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
|
-
|
71
|
-
|
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
|
)
|
easterobot/config.py
CHANGED
@@ -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,
|
@@ -30,7 +32,7 @@ V = TypeVar("V")
|
|
30
32
|
Members = Union[discord.Member, list[discord.Member]]
|
31
33
|
|
32
34
|
HERE = pathlib.Path(__file__).parent.resolve()
|
33
|
-
RESOURCES =
|
35
|
+
RESOURCES = HERE / "resources"
|
34
36
|
DEFAULT_CONFIG_PATH = pathlib.Path("config.yml")
|
35
37
|
EXAMPLE_CONFIG_PATH = RESOURCES / "config.example.yml"
|
36
38
|
|
@@ -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()
|
easterobot/games/connect.py
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
"""Connect4 and Connect3."""
|
2
2
|
|
3
|
-
import datetime
|
4
3
|
from functools import partial
|
5
4
|
from typing import Optional
|
6
5
|
|
7
6
|
import discord
|
8
|
-
from discord.utils import format_dt
|
9
7
|
from typing_extensions import override
|
10
8
|
|
11
9
|
from easterobot.games.game import Game, Player
|
10
|
+
from easterobot.utils import in_seconds
|
12
11
|
|
13
12
|
EMOJIS_MAPPER = {
|
14
13
|
"1️⃣": 0,
|
@@ -51,9 +50,9 @@ class Connect(Game):
|
|
51
50
|
async def on_start(self) -> None:
|
52
51
|
"""Run."""
|
53
52
|
await self.update()
|
53
|
+
await self.start_timer(61)
|
54
54
|
for emoji in EMOJIS[: self.cols]:
|
55
55
|
await self.message.add_reaction(emoji)
|
56
|
-
self.start_timer(60)
|
57
56
|
|
58
57
|
async def update(self) -> None:
|
59
58
|
"""Update the text."""
|
@@ -81,9 +80,8 @@ class Connect(Game):
|
|
81
80
|
for x in reversed(range(self.rows))
|
82
81
|
)
|
83
82
|
content += footer
|
84
|
-
now = datetime.datetime.now() + datetime.timedelta(seconds=62) # noqa: DTZ005
|
85
83
|
if not self.terminate:
|
86
|
-
content += f"\n\nFin du tour {
|
84
|
+
content += f"\n\nFin du tour {in_seconds(61)}"
|
87
85
|
embed = discord.Embed(description=content, color=self.color(player))
|
88
86
|
embed.set_author(
|
89
87
|
name="Partie terminée" if self.terminate else "Partie en cours",
|
@@ -95,7 +93,10 @@ class Connect(Game):
|
|
95
93
|
)
|
96
94
|
self.message = await self.message.edit(
|
97
95
|
embed=embed,
|
98
|
-
content=
|
96
|
+
content=(
|
97
|
+
f"-# {self.player1.member.mention} "
|
98
|
+
f"{self.player2.member.mention}"
|
99
|
+
),
|
99
100
|
view=None,
|
100
101
|
)
|
101
102
|
|
@@ -166,7 +167,7 @@ class Connect(Game):
|
|
166
167
|
await self.set_winner(None)
|
167
168
|
else:
|
168
169
|
self.turn += 1
|
169
|
-
self.start_timer(
|
170
|
+
await self.start_timer(61)
|
170
171
|
await self.update()
|
171
172
|
|
172
173
|
@override
|