From ac9953fed6484de413e626fd8d958f9892811cab Mon Sep 17 00:00:00 2001 From: Franz Rolfsvaag Date: Tue, 26 Aug 2025 00:07:51 +0200 Subject: [PATCH] 0.5.1.2.a4 - Minor patch to prevent non-initiated members from claiming crew roles --- bot.py | 2 +- modules/reaction_role/reaction_role.py | 29 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/bot.py b/bot.py index 84e3801..fe9525a 100644 --- a/bot.py +++ b/bot.py @@ -9,7 +9,7 @@ from modules.common.boot_notice import post_boot_notice # Version consists of: # Major.Enhancement.Minor.Patch.Test (Test is alphanumeric; doesn’t trigger auto update) -VERSION = "0.5.1.2.a3" +VERSION = "0.5.1.2.a4" # ---------- Env loading ---------- load_dotenv() diff --git a/modules/reaction_role/reaction_role.py b/modules/reaction_role/reaction_role.py index 08c0e20..79a7ba1 100644 --- a/modules/reaction_role/reaction_role.py +++ b/modules/reaction_role/reaction_role.py @@ -86,6 +86,7 @@ class ReactionRoleCog(commands.Cog): - Debounced nickname review to avoid duplicates when users add multiple accept emojis. - Fedaykin role is removed when the user unreacts the Fedaykin emoji. - Settings are reloaded dynamically on each event (hot-apply without restart). + - NEW: Only users with Full Access may claim/request crew roles. """ def __init__(self, bot): @@ -262,6 +263,25 @@ class ReactionRoleCog(commands.Cog): except Exception: return False + def _has_full_initiated(self, member: discord.Member) -> bool: + """User must have Full Access role to claim/request crew roles.""" + if not member or not isinstance(member.guild, discord.Guild): + return False + role = member.guild.get_role(self.full_access_role) if self.full_access_role else None + return bool(role and role in member.roles) + + async def _remove_reaction_silent(self, guild: discord.Guild, channel_id: int, message_id: int, + emoji: discord.PartialEmoji | discord.Emoji | str, member: discord.Member): + """Best-effort: remove a reaction without messaging the user.""" + try: + ch = guild.get_channel(channel_id) + if not isinstance(ch, (discord.TextChannel, discord.Thread)): + return + msg = await ch.fetch_message(message_id) + await msg.remove_reaction(emoji, member) + except Exception: + pass + async def maybe_apply_full_access(self, member: discord.Member): """Grant when Rules+RoE+Nickname *claimed*; revoke when any missing.""" guild = member.guild @@ -426,6 +446,10 @@ class ReactionRoleCog(commands.Cog): async def _post_fedaykin_card(self, guild: discord.Guild, member: discord.Member, hub_id: int) -> bool: """Post the Fedaykin approval card; return True if posted somewhere. While headless, queue pending only.""" + # Require Full Access to even request Fedaykin + if not self._has_full_initiated(member): + return False + # If headless or effectively headless (no Head members): queue silently if self._fedaykin_headless or not self._has_fedaykin_head(guild): await self._queue_pending(guild, member, reason="headless_runtime") @@ -737,6 +761,11 @@ class ReactionRoleCog(commands.Cog): # ----- Crew roles hub (custom emoji toggles + Fedaykin request) ----- try: if self.crew_msg_id and payload.message_id == self.crew_msg_id and payload.emoji.id: + # Gate: must have Full Access to claim/request crew roles + if not self._has_full_initiated(member): + await self._remove_reaction_silent(guild, payload.channel_id, payload.message_id, payload.emoji, member) + return + # Harvester / Escort if payload.emoji.id == self.emoji_harvest_id and self.role_harvest_id: role = guild.get_role(self.role_harvest_id)