From 55edbf7f0bece185781821da35b8f3254333facb Mon Sep 17 00:00:00 2001 From: johnvictorfs <37747572+johnvictorfs@users.noreply.github.com> Date: Mon, 2 Mar 2026 20:39:30 -0300 Subject: [PATCH 1/3] Allow authentication when ingame name is already linked to another Discord account Instead of blocking with a message to DM the bot owner, let the user proceed through authentication with extra world hops (+3 for pt, +2 for other languages). On success, the old Discord account's membership is disabled automatically. Co-Authored-By: Claude Sonnet 4.6 --- bot/cogs/authentication.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/bot/cogs/authentication.py b/bot/cogs/authentication.py index bfe680d..8f6fe2d 100644 --- a/bot/cogs/authentication.py +++ b/bot/cogs/authentication.py @@ -739,15 +739,12 @@ def check(message: discord.Message): ~Q(discord_id=str(ctx.author.id)), ingame_name__iexact=ingame_name.content ).first() + account_switch = False if user_ingame: self.logger.info( - f"[{ctx.author}] já existe usuário in-game autenticado com esse nome. ({user_ingame})" - ) - return await ctx.send( - "Já existe um Usuário do Discord autenticado com esse nome do jogo.\n" - "Caso seja mesmo o Dono dessa conta e acredite que outra pessoa tenha se cadastrado " - "com o seu nome por favor me contate aqui: <@148175892596785152>." + f"[{ctx.author}] já existe usuário in-game autenticado com esse nome, permitindo account switch. ({user_ingame})" ) + account_switch = True async with aiohttp.ClientSession() as cs: user_data = await get_user_data(ingame_name.content, cs) @@ -807,6 +804,8 @@ def check(message: discord.Message): worlds_requirement = random.randint(2, 3) if re_auth: worlds_requirement = 2 + if account_switch: + worlds_requirement += 3 if player_world["language"] == "pt" else 2 settings = { "f2p_worlds": player_world["f2p"], @@ -1088,6 +1087,14 @@ def confirm_check(reaction, user): await auth_chat.send(embed=confirm_embed) + if account_switch and user_ingame: + user_ingame.disabled = True + user_ingame.warning_date = None + user_ingame.save() + self.logger.info( + f"[{ctx.author}] Conta anterior desabilitada após account switch. ({user_ingame})" + ) + self.logger.info(f"[{ctx.author}] Autenticação finalizada.") await ctx.send(embed=self.feedback_embed()) From b3cbddfee90c15ee9162be35e1dc8c316e3a365f Mon Sep 17 00:00:00 2001 From: johnvictorfs <37747572+johnvictorfs@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:33:40 -0300 Subject: [PATCH 2/3] Modernize authentication command to use modals and buttons Replace reaction-based confirmations and wait_for message collection with discord.py UI components: a Modal for ingame name input (slash command path) and button Views for the ready confirmation and each world-hop step. Prefix command path keeps the existing text-based fallback. Co-Authored-By: Claude Sonnet 4.6 --- bot/cogs/authentication.py | 117 +++++++++++++++++++++++++------------ 1 file changed, 81 insertions(+), 36 deletions(-) diff --git a/bot/cogs/authentication.py b/bot/cogs/authentication.py index 8f6fe2d..72710cc 100644 --- a/bot/cogs/authentication.py +++ b/bot/cogs/authentication.py @@ -262,6 +262,49 @@ def settings_embed(settings: dict): return embed +class _IngameNameModal(discord.ui.Modal, title="Autenticação - Nome no Jogo"): + ingame_name = discord.ui.TextInput( + label="Nome no jogo (RuneScape 3)", + placeholder="Ex: Zezinho 123", + min_length=1, + max_length=12, + ) + + def __init__(self): + super().__init__(timeout=300) + self.interaction: Optional[discord.Interaction] = None + + async def on_submit(self, interaction: discord.Interaction): + self.interaction = interaction + self.stop() + + +class _ConfirmView(discord.ui.View): + def __init__(self, author_id: int, label: str = "Confirmar", *, timeout: float = 180): + super().__init__(timeout=timeout) + self.author_id = author_id + self.interaction: Optional[discord.Interaction] = None + for item in self.children: + if isinstance(item, discord.ui.Button): + item.label = label + break + + @discord.ui.button(label="Confirmar", style=discord.ButtonStyle.green, emoji="✅") + async def confirm(self, interaction: discord.Interaction, button: discord.ui.Button): + if interaction.user.id != self.author_id: + await interaction.response.send_message( + "Este botão não é para você.", ephemeral=True + ) + return + self.interaction = interaction + button.disabled = True + await interaction.response.edit_message(view=self) + self.stop() + + async def on_timeout(self): + self.stop() + + class UserAuthentication(commands.Cog): def __init__(self, bot: Bot): self.bot = bot @@ -716,27 +759,38 @@ async def aplicar_role(self, ctx: Context): "Você agora é um Membro do clã autenticado no Discord." ) - def check(message: discord.Message): - return message.author == ctx.author - - await ctx.send(f"{ctx.author.mention}, por favor me diga o seu nome no jogo.") - - try: - ingame_name = await self.bot.wait_for("message", timeout=180.0, check=check) - except asyncio.TimeoutError: - self.logger.info( - f"[{ctx.author}] Autenticação cancelada por Timeout (ingame_name)." - ) - return await ctx.send( - f"{ctx.author.mention}, autenticação cancelada. Tempo Esgotado." - ) + if ctx.interaction: + modal = _IngameNameModal() + await ctx.interaction.response.send_modal(modal) + await modal.wait() + if not modal.interaction: + self.logger.info( + f"[{ctx.author}] Autenticação cancelada por Timeout (ingame_name)." + ) + return + await modal.interaction.response.defer() + ingame_name_str = modal.ingame_name.value.strip() + else: + await ctx.send(f"{ctx.author.mention}, por favor me diga o seu nome no jogo.") + try: + ingame_name_msg = await self.bot.wait_for( + "message", timeout=180.0, check=lambda m: m.author == ctx.author + ) + except asyncio.TimeoutError: + self.logger.info( + f"[{ctx.author}] Autenticação cancelada por Timeout (ingame_name)." + ) + return await ctx.send( + f"{ctx.author.mention}, autenticação cancelada. Tempo Esgotado." + ) + ingame_name_str = ingame_name_msg.content.strip() - if len(ingame_name.content.strip()) > 12: + if len(ingame_name_str) > 12: return await ctx.send("Nome Inválido. Tamanho muito grande.") # Já existe outro usuário cadastrado com esse username in-game user_ingame = DiscordUser.objects.filter( - ~Q(discord_id=str(ctx.author.id)), ingame_name__iexact=ingame_name.content + ~Q(discord_id=str(ctx.author.id)), ingame_name__iexact=ingame_name_str ).first() account_switch = False @@ -747,10 +801,10 @@ def check(message: discord.Message): account_switch = True async with aiohttp.ClientSession() as cs: - user_data = await get_user_data(ingame_name.content, cs) + user_data = await get_user_data(ingame_name_str, cs) if not user_data: self.logger.info( - f"[{ctx.author}] Erro acessando API do RuneScape. ({ingame_name.content})" + f"[{ctx.author}] Erro acessando API do RuneScape. ({ingame_name_str})" ) return await ctx.send( "Houve um erro ao tentar acessar a API do RuneScape. Tente novamente mais tarde." @@ -815,19 +869,13 @@ def check(message: discord.Message): "failed_tries": 0, } - def confirm_check(reaction, user): - return user == ctx.author and str(reaction.emoji) == "✅" - settings_message = await ctx.send(embed=settings_embed(settings)) + ready_view = _ConfirmView(ctx.author.id, "Estou pronto", timeout=180) confirm_message = await ctx.send( - "Reaja nessa mensagem quando estiver pronto." + "Clique no botão quando estiver pronto para começar:", view=ready_view ) - await confirm_message.add_reaction("✅") - try: - await self.bot.wait_for( - "reaction_add", timeout=180, check=confirm_check - ) - except asyncio.TimeoutError: + await ready_view.wait() + if ready_view.interaction is None: self.logger.info( f"[{ctx.author}] Autenticação cancelada por Timeout. ({user_data})" ) @@ -854,18 +902,15 @@ def confirm_check(reaction, user): last_world = world + hop_view = _ConfirmView(ctx.author.id, "Já estou neste mundo", timeout=160) message: discord.Message = await ctx.send( f"{ctx.author.mention}, troque para o **Mundo {world['world']}**. " - f"Reaja na mensagem quando estiver nele." + f"Clique no botão quando estiver nele.", + view=hop_view, ) - await message.add_reaction("✅") - - try: - await self.bot.wait_for( - "reaction_add", timeout=160, check=confirm_check - ) - except asyncio.TimeoutError: + await hop_view.wait() + if hop_view.interaction is None: self.logger.info( f"[{ctx.author}] Autenticação cancelada por Timeout. (mundo) ({settings}) ({user_data})" ) From 5e86b5f7457d66e22c617c594499850ea4485855 Mon Sep 17 00:00:00 2001 From: johnvictorfs <37747572+johnvictorfs@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:39:51 -0300 Subject: [PATCH 3/3] Fix ruff linting errors across authentication, admin, and teams cogs - authentication.py: wrap long log string to stay under 120 chars (E501) - admin.py: expand single-line @app_commands.command decorator (E501) - teams.py: replace type() == comparison with isinstance() (E721) Co-Authored-By: Claude Sonnet 4.6 --- bot/cogs/admin.py | 5 ++++- bot/cogs/authentication.py | 3 ++- bot/cogs/teams.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bot/cogs/admin.py b/bot/cogs/admin.py index 52697f7..999cc41 100644 --- a/bot/cogs/admin.py +++ b/bot/cogs/admin.py @@ -592,7 +592,10 @@ async def sync(self, ctx: Context): await ctx.send("Slash commands sincronizados com sucesso.") @app_commands.check(is_pedim_or_nriver) - @app_commands.command(name="atl-spam", description="Envia uma mensagem no privado para todos os membros do servidor") + @app_commands.command( + name="atl-spam", + description="Envia uma mensagem no privado para todos os membros do servidor", + ) async def atl_spam( self, interaction: discord.Interaction, diff --git a/bot/cogs/authentication.py b/bot/cogs/authentication.py index 72710cc..28a82af 100644 --- a/bot/cogs/authentication.py +++ b/bot/cogs/authentication.py @@ -796,7 +796,8 @@ async def aplicar_role(self, ctx: Context): account_switch = False if user_ingame: self.logger.info( - f"[{ctx.author}] já existe usuário in-game autenticado com esse nome, permitindo account switch. ({user_ingame})" + f"[{ctx.author}] já existe usuário in-game autenticado com esse nome, " + f"permitindo account switch. ({user_ingame})" ) account_switch = True diff --git a/bot/cogs/teams.py b/bot/cogs/teams.py index 190077c..a7069ae 100644 --- a/bot/cogs/teams.py +++ b/bot/cogs/teams.py @@ -437,7 +437,7 @@ def role_check(message): else: field["value"] = answer.content - if type(field["value"]) == str and field["value"].lower() == "nenhum": + if isinstance(field["value"], str) and field["value"].lower() == "nenhum": field["value"] = None elif field["name"] == "role" or field["name"] == "role_secondary": field["value"] = re.search(r"\d+", answer.content).group()