Skip to content

Commit ce3b07c

Browse files
authored
Merge pull request #20 from ae-utbm/rate-limit
rate-limit prevention measures
2 parents 186b393 + 784d93a commit ce3b07c

3 files changed

Lines changed: 81 additions & 29 deletions

File tree

src/commands/club.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,11 @@ async def remove_club_member(
7373
club: Transform[ClubSchema, ClubTransformer],
7474
member: Member,
7575
):
76-
await interaction.response.defer(thinking=True)
77-
db_club = Club.get_or_none(Club.sith_id == club.id)
76+
await interaction.response.defer(thinking=True, ephemeral=True)
77+
db_club: Club = Club.get_or_none(Club.sith_id == club.id)
7878
if not db_club:
7979
await interaction.followup.send(f"Le club : {club.name} n'existe pas")
8080
return
81-
role_membre = utils.get(member.guild.roles, id=db_club.member_role_id)
8281
if (
8382
not interaction.user.guild_permissions.manage_roles
8483
and not interaction.user.get_role(db_club.president_role_id)
@@ -87,9 +86,19 @@ async def remove_club_member(
8786
"Seul le président du club et les admins peuvent retirer un membre"
8887
)
8988
return
90-
if role_membre not in member.roles:
89+
member_role = interaction.guild.get_role(db_club.member_role_id)
90+
board_roles = [db_club.president_role_id, db_club.treasurer_role_id]
91+
if member_role not in member.roles:
9192
await interaction.followup.send("Cet utilisateur n'est pas dans le club")
9293
return
94+
if any(member.get_role(r) for r in board_roles):
95+
await interaction.followup.send(
96+
"Cette commande ne peut pas être utilisée pour retirer "
97+
"le président ou le trésorier dun club.\n\n"
98+
"Utilisez plutôt `/club passation` ou `/club arret`."
99+
)
100+
return
101+
93102
await self.club_service.remove_member(db_club, member)
94103
await interaction.followup.send(
95104
f"{member.name} a été retiré du club :thumbs_up:"
@@ -107,9 +116,9 @@ async def add_club_member(
107116
club: Transform[ClubSchema, ClubTransformer],
108117
member: Member,
109118
):
110-
await interaction.response.defer(thinking=True)
119+
await interaction.response.defer(thinking=True, ephemeral=True)
111120
db_club = Club.get_or_none(Club.sith_id == club.id)
112-
role_membre = member.guild.get_role(db_club.member_role_id)
121+
role_membre = interaction.guild.get_role(db_club.member_role_id)
113122
if not db_club:
114123
await interaction.followup.send(f"Le club : {club.name} n'existe pas")
115124
return
@@ -139,7 +148,7 @@ async def add_club_member(
139148
async def create_club(
140149
self, interaction: Interaction, club: Transform[ClubSchema, ClubTransformer]
141150
):
142-
await interaction.response.defer(thinking=True)
151+
await interaction.response.defer(thinking=True, ephemeral=True)
143152
if Club.filter(Club.sith_id == club.id).exists():
144153
await interaction.followup.send(f"Le club : {club.name} existe déjà...")
145154
else:
@@ -176,7 +185,7 @@ async def handover(
176185
new_president: Member,
177186
new_treasurer: Member,
178187
):
179-
await interaction.response.defer(thinking=True)
188+
await interaction.response.defer(thinking=True, ephemeral=True)
180189
db_club = Club.get_or_none(Club.sith_id == club.id)
181190
guild = interaction.guild
182191

@@ -213,7 +222,7 @@ async def handover(
213222
async def stop_club(
214223
self, interaction: Interaction, club: Transform[ClubSchema, ClubTransformer]
215224
):
216-
await interaction.response.defer(thinking=True)
225+
await interaction.response.defer(thinking=True, ephemeral=True)
217226
db_club = Club.get_or_none(Club.sith_id == club.id)
218227
await self.club_service.stop_club(db_club, interaction.guild)
219228
annonce = await self.club_service.get_channel(

src/commands/misc.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
class MiscCog(commands.Cog):
1313
@app_commands.command(name="ping", description="Ping le bot")
1414
async def ping(self, interaction: Interaction[AeBot]):
15-
await interaction.response.send_message("pong")
15+
await interaction.response.send_message("pong", ephemeral=True)

src/services/club.py

Lines changed: 62 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import asyncio
34
from typing import TYPE_CHECKING
45
from urllib.parse import urljoin
56

@@ -35,6 +36,7 @@ def __init__(self, bot: AeBot):
3536
self._client = bot.client
3637
self._club_cache = {}
3738
self._bot = bot
39+
self._background_tasks = set()
3840

3941
async def search_club(
4042
self, current: str, *, only_existing: bool
@@ -135,13 +137,54 @@ async def add_member(self, club: Club, member: Member):
135137
async def remove_member(
136138
self, club: Club, member: Member, *, make_former: bool = True
137139
):
138-
role = utils.get(member.guild.roles, id=club.member_role_id)
139-
former = utils.get(member.guild.roles, id=club.former_member_role_id)
140-
await member.remove_roles(role, reason=f"{member.name} left club {club.name}")
141-
if make_former:
142-
await member.add_roles(
143-
former, reason=f"{member.name} left club {club.name}"
140+
"""Remove a member from the club.
141+
142+
Args:
143+
club: The club to remove the user from
144+
member: The member to remove
145+
make_former:
146+
if True, the member will receive
147+
a role indicating its former club membership
148+
"""
149+
await self.remove_members(club, [member], make_former=make_former)
150+
151+
async def remove_members(
152+
self,
153+
club: Club,
154+
members: list[Member] | tuple[Member] | set[Member],
155+
*,
156+
make_former: bool = True,
157+
):
158+
"""Remove multiple members from a club.
159+
160+
Args:
161+
club: The club to remove the user from
162+
members: The members to remove
163+
make_former:
164+
if True, the member will receive
165+
a role indicating its former club membership
166+
167+
Warnings:
168+
This method sleeps for two seconds between each member
169+
(in order to avoid rate-limit), so it may be a bad idea
170+
to await it.
171+
Favour an execution inside a detached async Task.
172+
"""
173+
role_ids = [club.member_role_id, club.president_role_id, club.treasurer_role_id]
174+
roles = [self._bot.watched_guild.get_role(r) for r in role_ids]
175+
former = self._bot.watched_guild.get_role(club.former_member_role_id)
176+
for member in members:
177+
if len(members) > 1:
178+
# if there is more than one member,
179+
# sleep a little bit to avoid rate limit
180+
await asyncio.sleep(2)
181+
await member.remove_roles(
182+
*roles, reason=f"{member.name} left club {club.name}"
144183
)
184+
if make_former:
185+
await member.add_roles(
186+
former, reason=f"{member.name} left club {club.name}"
187+
)
145188

146189
async def handover(
147190
self, club: ClubSchema, new_pres: Member, new_treso: Member, guild: Guild
@@ -176,25 +219,25 @@ async def stop_club(self, club: Club, guild: Guild):
176219
role_pres = utils.get(guild.roles, id=club.president_role_id)
177220
role_treso = utils.get(guild.roles, id=club.treasurer_role_id)
178221
role_member = utils.get(guild.roles, id=club.member_role_id)
179-
role_former = utils.get(guild.roles, id=club.former_member_role_id)
180-
old_member = {*role_pres.members, *role_treso.members, *role_member.members}
222+
old_members = {*role_pres.members, *role_treso.members, *role_member.members}
181223
category = utils.get(guild.categories, id=club.category_id)
182224
await self.move_to_bottom(category)
183225
await category.edit(name=club.name + " [inactif]")
184-
await self.move_to_bottom(category)
185-
186-
for e in old_member:
187-
await e.remove_roles(
188-
role_pres,
189-
role_treso,
190-
role_member,
191-
reason=f"Arrêt du club : {club.name}",
192-
)
193-
await e.add_roles(role_former, reason=f"Arrêt du club : {club.name}")
226+
# see https://docs.python.org/3/library/asyncio-task.html#creating-tasks
227+
task = asyncio.create_task(
228+
self.remove_members(club, old_members, make_former=True)
229+
)
230+
self._background_tasks.add(task)
231+
task.add_done_callback(self._background_tasks.discard)
194232

195233
@staticmethod
196234
async def move_to_bottom(category: CategoryChannel):
197-
"""Move this category after the last category belong to an active club."""
235+
"""Move this category after the last category belong to an active club.
236+
237+
Warnings:
238+
This method seems to have a high cost on discord's side.
239+
Using it a little bit too much is likely to end in rate-limit.
240+
"""
198241
guild = category.guild
199242
inactives = [c for c in guild.categories if c.name.endswith("[inactif]")]
200243
if not inactives:

0 commit comments

Comments
 (0)