From 85085325e23c12c2b6ba35c882aa265676512b4f Mon Sep 17 00:00:00 2001 From: DeveloperAnonymous <40847862+DeveloperAnonymous@users.noreply.github.com> Date: Sun, 8 Dec 2024 22:16:27 -0500 Subject: [PATCH] Add reaction role functionality Fixes #11 Add functionality for role assignment based on reactions. - Add `addreactionrole` and `removereactionrole` commands in `bot/botcommands/member.py` to link and unlink reactions to roles. - Update `bot/management/logging.py` to handle role assignment and removal based on reactions. - Add utility functions `add_reaction_role` and `remove_reaction_role` in `bot/util.py` for managing reaction roles. - Add `ReactionRole` model in `bot/db/models/reaction_role.py` to define the structure for reaction roles. - Add `ReactionRoleService` class in `bot/db/services/reaction_role_service.py` to handle database operations for reaction roles. --- For more details, open the [Copilot Workspace session](https://copilot-workspace.githubnext.com/ADEPT-Informatique/adeptbot/issues/11?shareId=XXXX-XXXX-XXXX-XXXX). --- bot/botcommands/member.py | 39 +++++++++++++ bot/db/models/reaction_role.py | 37 ++++++++++++ bot/db/services/reaction_role_service.py | 73 ++++++++++++++++++++++++ bot/management/logging.py | 29 ++++++++++ bot/util.py | 32 +++++++++++ 5 files changed, 210 insertions(+) create mode 100644 bot/db/models/reaction_role.py create mode 100644 bot/db/services/reaction_role_service.py diff --git a/bot/botcommands/member.py b/bot/botcommands/member.py index be62bb6..bfcd8d3 100644 --- a/bot/botcommands/member.py +++ b/bot/botcommands/member.py @@ -9,6 +9,7 @@ from bot.botcommands.utils.validators import has_at_least_role from bot.db.models.user import AdeptMember from bot.db.services.user_service import UserService +from bot.db.services.reaction_role_service import ReactionRoleService from bot.interactions import TicketOpeningInteraction from bot.interactions import ticket as ticket_interactions from bot.util import AdeptBotException @@ -19,6 +20,7 @@ class MemberCog(commands.Cog): def __init__(self) -> None: self.user_service = UserService() + self.reaction_role_service = ReactionRoleService() @commands.command() async def ticket(self, ctx: Context, ticket: tickets.TicketConverter): @@ -122,3 +124,40 @@ async def count_students_in_computer_science(self, ctx: Context): + f" - ``{decbac_students_count}`` en **DEC-BAC**\n\n" + f" - ``{former_student_count}`` anciens étudiants" ) + + @commands.command() + @has_at_least_role(configs.ADMIN_ROLE) + async def addreactionrole(self, ctx: Context, message_id: int, emoji: str, role_id: int): + """ + Cette commande permet d'ajouter une réaction à un message et de la lier à un rôle. + + Utilisation: + !addreactionrole + """ + message = await ctx.fetch_message(message_id) + role = ctx.guild.get_role(role_id) + + if not message or not role: + raise AdeptBotException("Message ou rôle invalide!") + + await message.add_reaction(emoji) + await self.reaction_role_service.add_reaction_role(message_id, emoji, role_id) + await ctx.send(f"Réaction {emoji} ajoutée au message {message_id} et liée au rôle {role.name}.") + + @commands.command() + @has_at_least_role(configs.ADMIN_ROLE) + async def removereactionrole(self, ctx: Context, message_id: int, emoji: str): + """ + Cette commande permet de retirer une réaction d'un message et de supprimer le lien avec un rôle. + + Utilisation: + !removereactionrole + """ + message = await ctx.fetch_message(message_id) + + if not message: + raise AdeptBotException("Message invalide!") + + await message.clear_reaction(emoji) + await self.reaction_role_service.remove_reaction_role(message_id, emoji) + await ctx.send(f"Réaction {emoji} retirée du message {message_id}.") diff --git a/bot/db/models/reaction_role.py b/bot/db/models/reaction_role.py new file mode 100644 index 0000000..3965f8c --- /dev/null +++ b/bot/db/models/reaction_role.py @@ -0,0 +1,37 @@ +"""ReactionRole model for linking reactions to roles.""" + +from bot.db.models.entity import Entity + + +class ReactionRole(Entity): + """ + ReactionRole model for linking reactions to roles. + + Attributes + ---------- + `message_id` : int + The ID of the message. + `emoji` : str + The emoji used for the reaction. + `role_id` : int + The ID of the role to assign. + """ + + __slots__ = ("message_id", "emoji", "role_id") + + def __init__(self, _id: int, message_id: int, emoji: str, role_id: int): + super().__init__(_id) + self.message_id = message_id + self.emoji = emoji + self.role_id = role_id + + def __getstate__(self): + state = super().__getstate__() + state.update( + { + "message_id": self.message_id, + "emoji": self.emoji, + "role_id": self.role_id, + } + ) + return state diff --git a/bot/db/services/reaction_role_service.py b/bot/db/services/reaction_role_service.py new file mode 100644 index 0000000..032e532 --- /dev/null +++ b/bot/db/services/reaction_role_service.py @@ -0,0 +1,73 @@ +"""Service class for ReactionRole model.""" + +from bot.db.services.base_service import BaseService +from bot.db.models.reaction_role import ReactionRole + + +class ReactionRoleService(BaseService): + """ + Service class for ReactionRole model. + + Methods + ------- + `add_reaction_role` : None + Add a reaction role to the database. + `remove_reaction_role` : None + Remove a reaction role from the database. + `get_reaction_role` : ReactionRole + Get a reaction role from the database. + """ + + @property + def collection_name(self): + return "reaction_roles" + + async def add_reaction_role(self, message_id: int, emoji: str, role_id: int): + """ + Add a reaction role to the database. + + Parameters + ---------- + `message_id` : int + The ID of the message. + `emoji` : str + The emoji used for the reaction. + `role_id` : int + The ID of the role to assign. + """ + reaction_role = ReactionRole(None, message_id, emoji, role_id) + await self.insert_one(reaction_role.__getstate__()) + + async def remove_reaction_role(self, message_id: int, emoji: str): + """ + Remove a reaction role from the database. + + Parameters + ---------- + `message_id` : int + The ID of the message. + `emoji` : str + The emoji used for the reaction. + """ + await self.delete_one({"message_id": message_id, "emoji": emoji}) + + async def get_reaction_role(self, message_id: int, emoji: str) -> ReactionRole: + """ + Get a reaction role from the database. + + Parameters + ---------- + `message_id` : int + The ID of the message. + `emoji` : str + The emoji used for the reaction. + + Returns + ------- + ReactionRole + The reaction role. + """ + result = await self.find_one({"message_id": message_id, "emoji": emoji}) + if result: + return ReactionRole(result["_id"], result["message_id"], result["emoji"], result["role_id"]) + return None diff --git a/bot/management/logging.py b/bot/management/logging.py index 1873ff2..8bbb277 100644 --- a/bot/management/logging.py +++ b/bot/management/logging.py @@ -5,11 +5,15 @@ import configs from bot import util +from bot.db.services.reaction_role_service import ReactionRoleService class LoggingCog(commands.Cog): """This class contains the events related to logging.""" + def __init__(self): + self.reaction_role_service = ReactionRoleService() + @commands.Cog.listener() async def on_message_edit(self, before: discord.Message, after: discord.Message): """This event is called when a message is edited.""" @@ -135,3 +139,28 @@ async def on_guild_channel_update(self, before: discord.abc.GuildChannel, after: embed.description = f"**``#{before}`` a été renommé pour {after.mention}**" await util.say(configs.LOGS_CHANNEL, embed=embed) + + @commands.Cog.listener() + async def on_raw_reaction_add(self, payload: discord.RawReactionActionEvent): + """This event is called when a reaction is added to a message.""" + if payload.member.bot: + return + + reaction_role = await self.reaction_role_service.get_reaction_role(payload.message_id, str(payload.emoji)) + if reaction_role: + guild = self.bot.get_guild(payload.guild_id) + role = guild.get_role(reaction_role.role_id) + await payload.member.add_roles(role) + + @commands.Cog.listener() + async def on_raw_reaction_remove(self, payload: discord.RawReactionActionEvent): + """This event is called when a reaction is removed from a message.""" + guild = self.bot.get_guild(payload.guild_id) + member = guild.get_member(payload.user_id) + if member.bot: + return + + reaction_role = await self.reaction_role_service.get_reaction_role(payload.message_id, str(payload.emoji)) + if reaction_role: + role = guild.get_role(reaction_role.role_id) + await member.remove_roles(role) diff --git a/bot/util.py b/bot/util.py index 1dbeba7..bab2831 100644 --- a/bot/util.py +++ b/bot/util.py @@ -281,3 +281,35 @@ def load(loaded_client): """ global CLIENT CLIENT = loaded_client + + +async def add_reaction_role(message_id: int, emoji: str, role_id: int): + """ + Add a reaction role. + + Parameters + ---------- + `message_id` : int + The id of the message to add the reaction to. + `emoji` : str + The emoji to add as a reaction. + `role_id` : int + The id of the role to assign when the reaction is added. + """ + reaction_role_service = ReactionRoleService() + await reaction_role_service.add_reaction_role(message_id, emoji, role_id) + + +async def remove_reaction_role(message_id: int, emoji: str): + """ + Remove a reaction role. + + Parameters + ---------- + `message_id` : int + The id of the message to remove the reaction from. + `emoji` : str + The emoji to remove as a reaction. + """ + reaction_role_service = ReactionRoleService() + await reaction_role_service.remove_reaction_role(message_id, emoji)