diff --git a/bot.py b/bot.py index 1ad5e28..c85e294 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.3.9.3.a3" +VERSION = "0.3.9.3.a4" # ---------- Env loading ---------- diff --git a/modules/nick_nudge/nick_nudge.py b/modules/nick_nudge/nick_nudge.py index b017696..d0fe2ff 100644 --- a/modules/nick_nudge/nick_nudge.py +++ b/modules/nick_nudge/nick_nudge.py @@ -394,7 +394,7 @@ class NickNudgeCog(commands.Cog): except Exception: pass - # ---------- Mod command to clear pending reviews from datafile ---------- + # ---------- Mod commands to manipulate nickname reviews ---------- @app_commands.command(name="clear_nick_reviews", description="Delete all PENDING nickname review records for this server.") async def clear_nick_reviews(self, interaction: discord.Interaction): @@ -437,5 +437,97 @@ class NickNudgeCog(commands.Cog): ephemeral=True ) + @app_commands.command(name="recreate_nick_reviews", description="Scan and recreate any missing pending nickname reviews for this server.") + async def recreate_nick_reviews(self, interaction: discord.Interaction): + """Moderator-only bulk fixer for 'grey checkmark' users (claimed but no pending review).""" + if not interaction.guild: + return await interaction.response.send_message("Use this in a server.", ephemeral=True) + if not await require_mod_interaction(interaction): + return # already replied + + dm = self.bot.data_manager + guild = interaction.guild + + agreed = set(dm.get('agreed_nickname')) + verified = set(dm.get('nick_verified')) + # Build a quick lookup of existing pending reviews + pending_reviews = { + (r.get('guild_id'), r.get('user_id')) + for r in dm.get('nick_reviews') + if r.get('status') == 'pending' + } + + to_fix = [] + for uid in agreed: + # Needs a review if not verified and no pending review exists + if (guild.id, uid) not in pending_reviews and uid not in verified: + m = guild.get_member(uid) + if m and not m.bot: + to_fix.append(m) + + fixed = 0 + skipped = 0 + for member in to_fix: + try: + # Clear stale pending so the atomic method will transition and open a new one + dm.remove('nick_claim_pending', lambda x, _uid=member.id: x == _uid) + await self.ensure_pending_and_maybe_open(guild, member, source="recreate") + fixed += 1 + except Exception: + skipped += 1 + + try: + await self._modlog(guild, f"🛠️ {interaction.user.mention} recreated **{fixed}** nickname review(s); skipped **{skipped}**.") + except Exception: + pass + + await interaction.response.send_message( + f"Recreated **{fixed}** review(s); skipped **{skipped}**.", + ephemeral=True + ) + + @app_commands.command(name="recreate", description="Recreate a missing pending nickname review for one user.") + @app_commands.describe(user="Member to recreate review for") + async def recreate_nick_review(self, interaction: discord.Interaction, user: discord.Member): + """Moderator-only single-user fixer.""" + if not interaction.guild: + return await interaction.response.send_message("Use this in a server.", ephemeral=True) + if not await require_mod_interaction(interaction): + return # already replied + + guild = interaction.guild + dm = self.bot.data_manager + + # If already verified, nothing to do + if user.id in dm.get('nick_verified'): + return await interaction.response.send_message("User is already verified — no review needed.", ephemeral=True) + + # If a pending review already exists, nothing to do + has_pending = any( + r.get('guild_id') == guild.id and r.get('user_id') == user.id and r.get('status') == 'pending' + for r in dm.get('nick_reviews') + ) + if has_pending: + return await interaction.response.send_message("A pending review already exists for this user.", ephemeral=True) + + # If they never agreed/claimed, mark claim now so the state is consistent + if user.id not in dm.get('agreed_nickname'): + dm.add('agreed_nickname', int(user.id)) + + # Clear stale pending flag, then open atomically + dm.remove('nick_claim_pending', lambda x: x == user.id) + + try: + await self.ensure_pending_and_maybe_open(guild, user, source="recreate") + except Exception: + return await interaction.response.send_message("Failed to create the review (see logs).", ephemeral=True) + + try: + await self._modlog(guild, f"🛠️ {interaction.user.mention} recreated a nickname review for {user.mention}.") + except Exception: + pass + + await interaction.response.send_message("Recreated the nickname review for that user.", ephemeral=True) + async def setup(bot): await bot.add_cog(NickNudgeCog(bot))