diff --git a/xp/sync.py b/xp/sync.py index fa62d56..0e3d79d 100644 --- a/xp/sync.py +++ b/xp/sync.py @@ -7,6 +7,15 @@ from .utils import load_config, xp_for_level from discord.utils import get import traceback import asyncio +import os + +# Load admin role IDs (same as in moderation base) +ADMIN_ROLE_IDS = { + int(role_id.strip()) + for role_id in os.getenv("ADMIN_ROLE_IDS", "").split(",") + if role_id.strip().isdigit() +} +LILAC_ID = 252130669919076352 class XPSync(commands.Cog): """Sync XP role rewards for users.""" @@ -14,6 +23,15 @@ class XPSync(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot + def check_admin_permission(self, user: discord.Member) -> bool: + """Check if user has admin permissions.""" + # Check if user is Lilac + if user.id == LILAC_ID: + return True + + # Check if user has admin role + return any(role.id in ADMIN_ROLE_IDS for role in user.roles) + async def sync_roles_for_user(self, member: discord.Member) -> tuple[int, list[str]]: """Sync roles for a member based on their lifetime XP level.""" config = load_config() @@ -63,21 +81,30 @@ class XPSync(commands.Cog): user: discord.User | None = None, ): try: - # Defer immediately - this is critical - await interaction.response.defer(ephemeral=False) - print(f"[SYNC] Deferred interaction for user {interaction.user.id}") - # Determine target user target_user = user if user else interaction.user - # If syncing someone else, check admin permission + # If syncing someone else, check admin permission BEFORE deferring if user and user.id != interaction.user.id: - # Use the is_admin decorator's check manually - try: - await ModerationBase.is_admin()(lambda: None).__wrapped__(interaction) - except Exception: - # The decorator already sends the error message + # Get the member object for permission checking + if not isinstance(interaction.user, discord.Member): + await interaction.response.send_message( + "❌ Cannot verify permissions in DMs.", + ephemeral=True + ) return + + # Check admin permissions + if not self.check_admin_permission(interaction.user): + await interaction.response.send_message( + "❌ You do not have permission to sync roles for other users.", + ephemeral=True + ) + return + + # NOW defer the interaction after permission checks pass + await interaction.response.defer(ephemeral=False) + print(f"[SYNC] Deferred interaction for user {interaction.user.id}") # Fetch target member try: @@ -86,10 +113,16 @@ class XPSync(commands.Cog): print(f"[SYNC] Member not in cache, fetching from API") target_member = await interaction.guild.fetch_member(target_user.id) except discord.NotFound: - return await interaction.followup.send(f"{target_user.mention} is not in this server.", ephemeral=True) + return await interaction.followup.send( + f"{target_user.mention} is not in this server.", + ephemeral=True + ) except discord.HTTPException as e: print(f"[SYNC] HTTP error fetching member: {e}") - return await interaction.followup.send(f"Error fetching member: {e}", ephemeral=True) + return await interaction.followup.send( + f"Error fetching member: {e}", + ephemeral=True + ) print(f"[SYNC] Starting role sync for {target_member.id}") @@ -101,13 +134,18 @@ class XPSync(commands.Cog): ) except asyncio.TimeoutError: print(f"[SYNC] Timeout during role sync for {target_member.id}") - return await interaction.followup.send("Role sync took too long. Please try again or contact an admin.", ephemeral=True) + return await interaction.followup.send( + "Role sync took too long. Please try again or contact an admin.", + ephemeral=True + ) print(f"[SYNC] Completed sync for {target_member.id}: Level {level}, Roles added: {roles_added}") # Send response if level == 0: - await interaction.followup.send(f"{target_member.mention} has no lifetime XP recorded.") + await interaction.followup.send( + f"{target_member.mention} has no lifetime XP recorded." + ) elif roles_added: await interaction.followup.send( f"Synced roles for {target_member.mention} (Level {level})\n" @@ -125,7 +163,10 @@ class XPSync(commands.Cog): print(f"[SYNC] Unexpected error in sync command: {e}") traceback.print_exc() try: - await interaction.followup.send(f"Error syncing roles: {str(e)[:100]}", ephemeral=True) + await interaction.followup.send( + f"Error syncing roles: {str(e)[:100]}", + ephemeral=True + ) except Exception as followup_error: print(f"[SYNC] Could not send error message: {followup_error}")