import disnake from disnake.ext import commands from disnake.ui import View, Button import json import os from datetime import datetime from typing import List, Dict, Optional class WhitelistMonitor(commands.Cog): """Monitors whitelist members for role changes and server leave events.""" def __init__(self, bot): self.bot = bot self.whitelist_file = "data/whitelist.json" self.persistent_views_added = False self.big_permissions = [ "administrator", "manage_guild", "manage_roles", "manage_channels", "kick_members", "ban_members", "manage_messages", "moderate_members", "manage_nicknames", "manage_emojis", "manage_webhooks", "view_audit_log" ] def load_whitelist(self) -> Dict[str, List[str]]: """Load whitelist from JSON file.""" if not os.path.exists(self.whitelist_file): return {} try: with open(self.whitelist_file, "r", encoding="utf-8") as f: raw_data = json.load(f) # Convert all user IDs to strings for consistent comparison result = {} for guild_id, user_ids in raw_data.items(): result[str(guild_id)] = [str(uid) for uid in user_ids] return result except (json.JSONDecodeError, FileNotFoundError): return {} def save_whitelist(self, whitelist: Dict[str, List[str]]): """Save whitelist to JSON file.""" with open(self.whitelist_file, "w", encoding="utf-8") as f: json.dump(whitelist, f, ensure_ascii=False, indent=4) def is_whitelisted(self, guild_id: int, user_id: int) -> bool: """Check if user is whitelisted for a specific guild.""" whitelist = self.load_whitelist() guild_key = str(guild_id) user_key = str(user_id) return guild_key in whitelist and user_key in whitelist[guild_key] def remove_from_whitelist(self, guild_id: int, user_id: int) -> bool: """Remove user from whitelist for a specific guild.""" whitelist = self.load_whitelist() guild_key = str(guild_id) user_key = str(user_id) if guild_key in whitelist and user_key in whitelist[guild_key]: whitelist[guild_key].remove(user_key) # Clean up empty guild entries if not whitelist[guild_key]: del whitelist[guild_key] self.save_whitelist(whitelist) return True return False def has_big_permissions(self, role: disnake.Role) -> bool: """Check if role has significant permissions.""" perms = role.permissions return any(getattr(perms, p, False) for p in self.big_permissions) async def send_whitelist_notification(self, member: disnake.Member, removed_roles: List[disnake.Role]): """Send DM notification to user about whitelist member losing roles.""" try: # Find guild administrators to notify admins = [m for m in member.guild.members if m.guild_permissions.administrator and not m.bot] if not admins: return # Create embed embed = disnake.Embed( title="⚠️ Whitelist Alert", description=f"Un membre whitelisté a perdu des rôles importants dans **{member.guild.name}**.", color=disnake.Color.orange(), timestamp=datetime.now() ) embed.add_field( name="👤 Membre concerné", value=f"{member.mention} ({member.display_name})", inline=False ) if removed_roles: roles_text = "\n".join([f"• {role.name}" for role in removed_roles]) embed.add_field( name="🗑️ Rôles retirés", value=roles_text, inline=False ) embed.add_field( name="📋 Statut whitelist", value="✅ Toujours whitelisté", inline=True ) # Create view with removal button view = WhitelistRemovalView(member.id, member.guild.id) # Send to all admins for admin in admins: try: await admin.send(embed=embed, view=view) except disnake.Forbidden: continue # User has DMs disabled except Exception as e: print(f"Error sending whitelist notification: {e}") async def cog_load(self): """Register persistent views when cog is loaded""" await self._register_persistent_views() async def _register_persistent_views(self): """Register all persistent views for guilds with whitelist alerts""" if self.persistent_views_added: return try: import json security_file = "data/security_settings.json" if os.path.exists(security_file): with open(security_file, "r", encoding="utf-8") as f: all_settings = json.load(f) # For each guild with whitelist monitoring, register a default view # We'll use a generic view that gets recreated when needed # The key is that each unique custom_id needs to be registered once view = WhitelistRemovalView(0, 0) # Placeholder with dummy IDs self.bot.add_view(view) print(f"[WhitelistMonitor] Persistent view registered") self.persistent_views_added = True except Exception as e: print(f"[WhitelistMonitor] Error registering views: {e}") @commands.Cog.listener() async def on_member_update(self, before: disnake.Member, after: disnake.Member): """Monitor role changes for whitelist members.""" if before.roles == after.roles: return if not self.is_whitelisted(after.guild.id, after.id): return # Find removed roles removed_roles = [role for role in before.roles if role not in after.roles] # Check if any removed roles had big permissions important_removed = [role for role in removed_roles if self.has_big_permissions(role)] if important_removed: await self.send_whitelist_notification(after, important_removed) @commands.Cog.listener() async def on_member_remove(self, member: disnake.Member): """Auto-remove from whitelist when member leaves server.""" if self.is_whitelisted(member.guild.id, member.id): removed = self.remove_from_whitelist(member.guild.id, member.id) if removed: # Log the removal try: log_channel_id = self.get_log_channel(member.guild.id) if log_channel_id: log_channel = member.guild.get_channel(log_channel_id) if log_channel: embed = disnake.Embed( title="🗑️ Auto-Whitelist Removal", description=f"{member.mention} a été retiré automatiquement de la whitelist après avoir quitté le serveur.", color=disnake.Color.red(), timestamp=datetime.now() ) await log_channel.send(embed=embed) except Exception as e: print(f"Error logging whitelist removal: {e}") def get_log_channel(self, guild_id: int) -> Optional[int]: """Get log channel ID from security settings.""" try: security_file = "data/security_settings.json" if os.path.exists(security_file): with open(security_file, "r") as f: settings = json.load(f) return settings.get(str(guild_id), {}).get("log_channel_id") except: pass return None class WhitelistRemovalView(View): """View with button to remove user from whitelist.""" def __init__(self, user_id: int, guild_id: int): super().__init__(timeout=None) # Persistent view self.user_id = user_id self.guild_id = guild_id @disnake.ui.button( label="Retirer de la whitelist", style=disnake.ButtonStyle.red, emoji="🗑️", custom_id="whitelist_remove_button" ) async def remove_whitelist( self, interaction: disnake.Interaction, button: Button ): """Remove user from whitelist when button is clicked.""" try: # Get the cog instance cog = self.bot.get_cog("WhitelistMonitor") if not cog: await interaction.response.send_message( "❌ Erreur: Système de whitelist non disponible.", ephemeral=True ) return # Remove from whitelist removed = cog.remove_from_whitelist(self.guild_id, self.user_id) if removed: # Get user info try: user = await self.bot.fetch_user(self.user_id) user_mention = user.mention except: user_mention = f"<@{self.user_id}>" # Update button button.disabled = True button.label = "✅ Retiré" button.style = disnake.ButtonStyle.green await interaction.response.edit_message(view=self) # Send confirmation embed = disnake.Embed( title="✅ Retrait effectué", description=f"{user_mention} a été retiré de la whitelist.", color=disnake.Color.green() ) await interaction.followup.send(embed=embed, ephemeral=True) # Log the removal try: guild = self.bot.get_guild(self.guild_id) if guild: log_channel_id = cog.get_log_channel(self.guild_id) if log_channel_id: log_channel = guild.get_channel(log_channel_id) if log_channel: embed = disnake.Embed( title="🗑️ Whitelist Manual Removal", description=f"{user_mention} a été retiré manuellement de la whitelist.", color=disnake.Color.green(), timestamp=datetime.now() ) embed.set_footer(text=f"Retiré par {interaction.user.display_name}") await log_channel.send(embed=embed) except Exception as e: print(f"Error logging manual removal: {e}") else: await interaction.response.send_message( "ℹ️ L'utilisateur n'était pas dans la whitelist.", ephemeral=True ) except Exception as e: await interaction.response.send_message( f"❌ Erreur lors du retrait: {str(e)}", ephemeral=True ) def setup(bot): bot.add_cog(WhitelistMonitor(bot))