import disnake from disnake.ext import commands import json import os from typing import Optional # Chemins des fichiers SETTINGS_FILE = "data/security_settings.json" WHITELIST_FILE = "data/whitelist.json" # --- UTILITAIRES --- def load_settings(guild_id: int) -> dict: """Charge les paramètres de sécurité pour un serveur""" if not os.path.exists(SETTINGS_FILE): return {} try: with open(SETTINGS_FILE, "r", encoding='utf-8') as f: return json.load(f).get(str(guild_id), {}) except (json.JSONDecodeError, FileNotFoundError): return {} def save_settings(guild_id: int, settings: dict) -> None: """Sauvegarde les paramètres de sécurité""" data = {} if os.path.exists(SETTINGS_FILE): try: with open(SETTINGS_FILE, "r", encoding='utf-8') as f: data = json.load(f) except (json.JSONDecodeError, FileNotFoundError): pass data[str(guild_id)] = settings os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True) with open(SETTINGS_FILE, "w", encoding='utf-8') as f: json.dump(data, f, indent=4, ensure_ascii=False) def load_whitelist(guild_id: int) -> list: """Charge la whitelist pour un serveur""" if not os.path.exists(WHITELIST_FILE): return [] try: with open(WHITELIST_FILE, "r", encoding='utf-8') as f: data = json.load(f) return data.get(str(guild_id), []) except (json.JSONDecodeError, FileNotFoundError): return [] def is_whitelisted(guild_id: int, user_id: int) -> bool: """Vérifie si un utilisateur est whitelisté""" if not os.path.exists(WHITELIST_FILE): return False try: with open(WHITELIST_FILE, "r", encoding='utf-8') as f: data = json.load(f) return user_id in data.get(str(guild_id), []) except (json.JSONDecodeError, FileNotFoundError): return False def is_immune(guild, user) -> bool: """Vérifie l'immunité (propriétaire, admin, ou whitelist)""" if user.id == guild.owner_id: return True if user.guild_permissions.administrator: return True if is_whitelisted(guild.id, user.id): return True return False # --- MODALS DE CONFIGURATION --- class SpamThresholdModal(disnake.ui.Modal): """Modal pour configurer le seuil anti-spam""" threshold_input = disnake.ui.TextInput( label="Messages max autorisés", placeholder="Entre 3 et 10 (défaut: 5)", min_length=1, max_length=2, required=True ) def __init__(self, guild_id: int, settings: dict, view: 'SecurityView'): super().__init__(title="🛡️ Seuil Anti-Spam", components=[self.threshold_input]) self.guild_id = guild_id self.settings = settings self.parent_view = view async def callback(self, interaction: disnake.Interaction): if not self.threshold_input.value.isdigit(): return await interaction.response.send_message( "❌ Erreur : Veuillez entrer un nombre entier.", ephemeral=True ) value = int(self.threshold_input.value) if value < 1 or value > 10: return await interaction.response.send_message( "❌ Erreur : La valeur doit être entre 1 et 10.", ephemeral=True ) self.settings["spam_threshold"] = value save_settings(self.guild_id, self.settings) self.parent_view.settings = self.settings self.parent_view.build_view() await interaction.response.edit_message(view=self.parent_view) await interaction.followup.send( f"✅ Seuil anti-spam configuré sur **{value}** messages/5s", ephemeral=True ) class RaidThresholdModal(disnake.ui.Modal): """Modal pour configurer le seuil anti-raid""" threshold_input = disnake.ui.TextInput( label="Joins max en 10 secondes", placeholder="Entre 2 et 10 (défaut: 3)", min_length=1, max_length=2, required=True ) def __init__(self, guild_id: int, settings: dict, view: 'SecurityView'): super().__init__(title="⚔️ Seuil Anti-Raid", components=[self.threshold_input]) self.guild_id = guild_id self.settings = settings self.parent_view = view async def callback(self, interaction: disnake.Interaction): if not self.threshold_input.value.isdigit(): return await interaction.response.send_message( "❌ Erreur : Veuillez entrer un nombre entier.", ephemeral=True ) value = int(self.threshold_input.value) if value < 1 or value > 20: return await interaction.response.send_message( "❌ Erreur : La valeur doit être entre 1 et 20.", ephemeral=True ) self.settings["raid_threshold"] = value save_settings(self.guild_id, self.settings) self.parent_view.settings = self.settings self.parent_view.build_view() await interaction.response.edit_message(view=self.parent_view) await interaction.followup.send( f"✅ Seuil anti-raid configuré sur **{value}** joins/10s", ephemeral=True ) # --- MODALS DE CONFIGURATION WHITELIST --- class WhitelistModal(disnake.ui.Modal): """Modal pour ajouter un utilisateur à la whitelist""" user_input = disnake.ui.TextInput( label="ID de l'utilisateur", placeholder="Entrez l'ID Discord de l'utilisateur", min_length=15, max_length=20, required=True ) def __init__(self, guild_id: int, view: 'SecurityView'): super().__init__(title="👤 Ajouter à la Whitelist", components=[self.user_input]) self.guild_id = guild_id self.parent_view = view async def callback(self, interaction: disnake.Interaction): if not self.user_input.value.isdigit(): return await interaction.response.send_message( "❌ Erreur : ID invalide.", ephemeral=True ) user_id = int(self.user_input.value) # Charger et modifier la whitelist data = {} if os.path.exists(WHITELIST_FILE): try: with open(WHITELIST_FILE, "r", encoding='utf-8') as f: data = json.load(f) except (json.JSONDecodeError, FileNotFoundError): pass guild_whitelist = data.get(str(self.guild_id), []) if user_id in guild_whitelist: return await interaction.response.send_message( "❌ Cet utilisateur est déjà dans la whitelist.", ephemeral=True ) guild_whitelist.append(user_id) data[str(self.guild_id)] = guild_whitelist os.makedirs(os.path.dirname(WHITELIST_FILE), exist_ok=True) with open(WHITELIST_FILE, "w", encoding='utf-8') as f: json.dump(data, f, indent=4, ensure_ascii=False) self.parent_view.whitelist_count = len(guild_whitelist) self.parent_view.build_view() await interaction.response.edit_message(view=self.parent_view) await interaction.followup.send( f"✅ Utilisateur ajouté à la whitelist.", ephemeral=True ) # --- VUES DE L'INTERFACE --- class SecurityView(disnake.ui.View): """Vue principale du panneau de sécurité""" def __init__(self, interaction: disnake.Interaction, settings: dict, category: Optional[str] = None, bot=None): super().__init__(timeout=300) # 5 minutes timeout self.interaction = interaction self.guild = interaction.guild self.settings = settings self.category = category self.bot = bot self.whitelist_count = len(load_whitelist(self.guild.id)) self.build_view() def build_view(self): """Construit les boutons selon la catégorie""" self.clear_items() if self.category is None: # Menu principal self.add_item(MainCategorySelect()) elif self.category == "spam": self.build_spam_view() elif self.category == "raid": self.build_raid_view() elif self.category == "nuke": self.build_nuke_view() elif self.category == "logs": self.build_logs_view() elif self.category == "whitelist": self.build_whitelist_view() elif self.category == "rules": self.build_rules_view() def build_spam_view(self): """Construit la vue Anti-Spam""" # Toggle Anti-Spam anti_spam = self.settings.get("anti_spam", False) btn_antispam = ToggleButton( label="Anti-Spam", emoji="🛡️", is_enabled=anti_spam, custom_id="anti_spam" ) self.add_item(btn_antispam) # Toggle Anti-Lien anti_link = self.settings.get("anti_link", False) btn_antilink = ToggleButton( label="Anti-Liens", emoji="🔗", is_enabled=anti_link, custom_id="anti_link" ) self.add_item(btn_antilink) # Bouton de configuration du seuil threshold = self.settings.get("spam_threshold", 5) btn_threshold = disnake.ui.Button( label=f"🔢 Seuil: {threshold} msg/5s", style=disnake.ButtonStyle.blurple, custom_id="spam_threshold" ) btn_threshold.callback = self.open_spam_threshold_modal self.add_item(btn_threshold) # Bouton retour btn_back = disnake.ui.Button( label="Retour", style=disnake.ButtonStyle.gray, emoji="⬅️" ) btn_back.callback = self.back_to_main self.add_item(btn_back) def build_raid_view(self): """Construit la vue Anti-Raid""" # Toggle Anti-Raid anti_raid = self.settings.get("anti_raid", False) btn_antiraid = ToggleButton( label="Anti-Raid", emoji="⚔️", is_enabled=anti_raid, custom_id="anti_raid" ) self.add_item(btn_antiraid) # Toggle Auto-Ban auto_ban = self.settings.get("auto_ban", False) btn_autoban = ToggleButton( label="Auto-Ban", emoji="🔨", is_enabled=auto_ban, custom_id="auto_ban" ) self.add_item(btn_autoban) # Bouton de configuration du seuil threshold = self.settings.get("raid_threshold", 3) btn_threshold = disnake.ui.Button( label=f"🔢 Seuil: {threshold} join/10s", style=disnake.ButtonStyle.blurple, custom_id="raid_threshold" ) btn_threshold.callback = self.open_raid_threshold_modal self.add_item(btn_threshold) # Bouton retour btn_back = disnake.ui.Button( label="Retour", style=disnake.ButtonStyle.gray, emoji="⬅️" ) btn_back.callback = self.back_to_main self.add_item(btn_back) def build_nuke_view(self): """Construit la vue Anti-Nuke""" # Toggle Anti-Salon anti_channel = self.settings.get("anti_channel_delete", False) btn_channel = ToggleButton( label="Anti-Salon", emoji="📁", is_enabled=anti_channel, custom_id="anti_channel_delete" ) self.add_item(btn_channel) # Toggle Anti-Rôle anti_role = self.settings.get("anti_role_create", False) btn_role = ToggleButton( label="Anti-Rôle", emoji="🎭", is_enabled=anti_role, custom_id="anti_role_create" ) self.add_item(btn_role) # Bouton retour btn_back = disnake.ui.Button( label="Retour", style=disnake.ButtonStyle.gray, emoji="⬅️" ) btn_back.callback = self.back_to_main self.add_item(btn_back) def build_logs_view(self): """Construit la vue Logs""" # Toggle Logs logs_enabled = self.settings.get("server_logs", False) btn_logs = ToggleButton( label="Logs Serveur", emoji="📋", is_enabled=logs_enabled, custom_id="server_logs" ) self.add_item(btn_logs) # ID du channel de logs (affichage) log_channel_id = self.settings.get("log_channel_id") if log_channel_id: channel = self.guild.get_channel(int(log_channel_id)) channel_mention = channel.mention if channel else f"`{log_channel_id}`" btn_channel = disnake.ui.Button( label=f"📌 Channel: {channel.name if channel else 'Invalide'}", style=disnake.ButtonStyle.green, disabled=True ) else: btn_channel = disnake.ui.Button( label="📌 Aucun channel configuré", style=disnake.ButtonStyle.red, disabled=True ) self.add_item(btn_channel) # Bouton retour btn_back = disnake.ui.Button( label="Retour", style=disnake.ButtonStyle.gray, emoji="⬅️" ) btn_back.callback = self.back_to_main self.add_item(btn_back) def build_whitelist_view(self): """Construit la vue Whitelist""" # Affichage du nombre avec style btn_count = disnake.ui.Button( label=f"👥 Whitelist: {self.whitelist_count} utilisateur(s)", style=disnake.ButtonStyle.blurple, disabled=True ) self.add_item(btn_count) # Menu dropdown pour gérer la whitelist if self.whitelist_count > 0: self.add_item(WhitelistSelect(self.guild, self)) else: # Message si whitelist vide btn_empty = disnake.ui.Button( label="📭 Aucun utilisateur dans la whitelist", style=disnake.ButtonStyle.gray, disabled=True ) self.add_item(btn_empty) # Bouton ajouter btn_add = disnake.ui.Button( label="➕ Ajouter", style=disnake.ButtonStyle.green, emoji="➕", custom_id="whitelist_add" ) btn_add.callback = self.open_whitelist_modal self.add_item(btn_add) # Bouton retour btn_back = disnake.ui.Button( label="Retour", style=disnake.ButtonStyle.gray, emoji="⬅️" ) btn_back.callback = self.back_to_main self.add_item(btn_back) def build_rules_view(self): """Construit la vue Règlement""" from commandes.modules_security.rules import ( RulesTitleModal, RulesContentModal, RulesChannelModal, RulesRoleModal, ToggleRulesButton, save_settings ) # Toggle Activer/Désactiver rules_enabled = self.settings.get("rules_enabled", False) btn_toggle = ToggleButton( label="Règlement", emoji="📜", is_enabled=rules_enabled, custom_id="rules_enabled" ) # Bouton pour modifier le titre btn_title = disnake.ui.Button( label="Éditer le titre", style=disnake.ButtonStyle.blurple, emoji="📝" ) btn_title.callback = self.open_rules_title_modal self.add_item(btn_title) # Bouton pour modifier le contenu btn_content = disnake.ui.Button( label="Éditer le contenu", style=disnake.ButtonStyle.blurple, emoji="📄" ) btn_content.callback = self.open_rules_content_modal self.add_item(btn_content) # Bouton pour choisir le salon rules_channel_id = self.settings.get("rules_channel_id") if rules_channel_id: channel = self.guild.get_channel(int(rules_channel_id)) btn_channel = disnake.ui.Button( label=f"Salon: {channel.name if channel else 'Invalide'}", style=disnake.ButtonStyle.green, custom_id="rules_channel" ) else: btn_channel = disnake.ui.Button( label="Aucun salon configuré", style=disnake.ButtonStyle.red, custom_id="rules_channel" ) btn_channel.callback = self.open_rules_channel_modal self.add_item(btn_channel) # Bouton pour choisir le rôle d'acceptation rules_role_id = self.settings.get("rules_accept_role_id") if rules_role_id: role = self.guild.get_role(int(rules_role_id)) btn_role = disnake.ui.Button( label=f"Rôle: {role.name if role else 'Invalide'}", style=disnake.ButtonStyle.green, custom_id="rules_role" ) else: btn_role = disnake.ui.Button( label="Aucun rôle configuré", style=disnake.ButtonStyle.red, custom_id="rules_role" ) btn_role.callback = self.open_rules_role_modal self.add_item(btn_role) # Bouton pour choisir le type de message message_type = self.settings.get("rules_message_type", "embed") type_label = "Embed" if message_type == "embed" else "Message simple" btn_message_type = disnake.ui.Button( label=f"Type: {type_label}", style=disnake.ButtonStyle.blurple, emoji="📝", custom_id="rules_message_type" ) btn_message_type.callback = self.open_rules_message_type_modal self.add_item(btn_message_type) # Bouton pour envoyer le règlement btn_send = disnake.ui.Button( label="Envoyer le règlement", style=disnake.ButtonStyle.green, emoji="🚀" ) btn_send.callback = self.send_rules self.add_item(btn_send) # Bouton retour btn_back = disnake.ui.Button( label="Retour", style=disnake.ButtonStyle.gray, emoji="⬅️" ) btn_back.callback = self.back_to_main self.add_item(btn_back) async def toggle_setting(self, interaction: disnake.Interaction, key: str): """Bascule un paramètre""" self.settings[key] = not self.settings.get(key, False) save_settings(self.guild.id, self.settings) self.build_view() await interaction.response.edit_message(view=self) async def open_spam_threshold_modal(self, interaction: disnake.Interaction): """Ouvre le modal de configuration du seuil spam""" await interaction.response.send_modal( SpamThresholdModal(self.guild.id, self.settings, self) ) async def open_raid_threshold_modal(self, interaction: disnake.Interaction): """Ouvre le modal de configuration du seuil raid""" await interaction.response.send_modal( RaidThresholdModal(self.guild.id, self.settings, self) ) async def open_whitelist_modal(self, interaction: disnake.Interaction): """Ouvre le modal d'ajout à la whitelist""" await interaction.response.send_modal( WhitelistModal(self.guild.id, self) ) async def open_rules_title_modal(self, interaction: disnake.Interaction): """Ouvre le modal de configuration du titre du règlement""" from .modules_security.rules import RulesTitleModal await interaction.response.send_modal( RulesTitleModal(self.guild.id, self.settings) ) async def open_rules_content_modal(self, interaction: disnake.Interaction): """Ouvre le modal de configuration du contenu du règlement""" from .modules_security.rules import RulesContentModal await interaction.response.send_modal( RulesContentModal(self.guild.id, self.settings) ) async def open_rules_channel_modal(self, interaction: disnake.Interaction): """Ouvre le modal de sélection du salon du règlement""" from .modules_security.rules import RulesChannelModal await interaction.response.send_modal( RulesChannelModal(self.guild.id, self.settings) ) async def open_rules_role_modal(self, interaction: disnake.Interaction): """Ouvre le modal de configuration du rôle d'acceptation""" from .modules_security.rules import RulesRoleModal await interaction.response.send_modal( RulesRoleModal(self.guild.id, self.settings) ) async def open_rules_message_type_modal(self, interaction: disnake.Interaction): """Ouvre le modal de sélection du type de message""" from .modules_security.rules import RulesMessageTypeModal await interaction.response.send_modal( RulesMessageTypeModal(self.guild.id, self.settings) ) async def send_rules(self, interaction: disnake.Interaction): """Envoie le règlement dans le salon configuré""" from commandes.modules_security.rules import RulesCog # Vérifier que tous les paramètres sont configurés rules_channel_id = self.settings.get("rules_channel_id") rules_accept_role_id = self.settings.get("rules_accept_role_id") if not rules_channel_id: return await interaction.response.send_message( "❌ Aucun salon configuré pour l'envoi du règlement.", ephemeral=True ) if not rules_accept_role_id: return await interaction.response.send_message( "❌ Aucun rôle d'acceptation configuré.", ephemeral=True ) # Vérifier le salon channel = self.guild.get_channel(int(rules_channel_id)) if not channel: return await interaction.response.send_message( "❌ Le salon configuré n'existe plus.", ephemeral=True ) # Vérifier le rôle role = self.guild.get_role(int(rules_accept_role_id)) if not role: return await interaction.response.send_message( "❌ Le rôle d'acceptation n'existe plus.", ephemeral=True ) # Envoyer le règlement try: rules_cog = self.bot.get_cog("RulesCog") if rules_cog: message = await rules_cog.send_rules_message(channel, self.settings) if message: # Activer le règlement self.settings["rules_enabled"] = True save_settings(self.guild.id, self.settings) self.settings = load_settings(self.guild.id) self.build_view() await interaction.response.edit_message(view=self) await interaction.followup.send( f"✅ Règlement envoyé avec succès dans {channel.mention} !", ephemeral=True ) else: await interaction.followup.send( "❌ Erreur lors de l'envoi du règlement.", ephemeral=True ) else: await interaction.followup.send( "❌ Le cog Rules n'est pas chargé.", ephemeral=True ) except Exception as e: await interaction.followup.send( f"❌ Erreur: {e}", ephemeral=True ) async def back_to_main(self, interaction: disnake.Interaction): """Retour au menu principal""" self.category = None self.build_view() embed = self.create_main_embed(interaction.user) await interaction.response.edit_message(embed=embed, view=self) def create_main_embed(self, user: disnake.Member = None) -> disnake.Embed: """Crée l'embed du menu principal""" embed = disnake.Embed( title="🛡️ Centre de Sécurité Kuby", description="Gérez la protection de votre serveur", color=0x7289DA ) if self.guild.icon: embed.set_thumbnail(url=self.guild.icon.url) # Informations du serveur embed.add_field( name="🏠 Serveur", value=self.guild.name, inline=True ) embed.add_field( name="👤 Propriétaire", value=self.guild.owner.mention if self.guild.owner else "Inconnu", inline=True ) # Statut global active_count = sum([ self.settings.get("anti_spam", False), self.settings.get("anti_link", False), self.settings.get("anti_raid", False), self.settings.get("auto_ban", False), self.settings.get("anti_channel_delete", False), self.settings.get("anti_role_create", False), self.settings.get("server_logs", False) ]) total_options = 7 status = "🟢 Actif" if active_count > 0 else "🔴 Inactif" embed.add_field( name="⚡ Protection", value=f"{status} ({active_count}/{total_options})", inline=True ) # Avertissement si pas admin if user and not is_immune(self.guild, user): embed.set_footer(text="⚠️ Vous n'êtes pas administrateur. Accès restreint.") return embed def create_category_embed(self, title: str, description: str) -> disnake.Embed: """Crée un embed pour une catégorie""" embed = disnake.Embed( title=title, description=description, color=0x7289DA ) if self.guild.icon: embed.set_thumbnail(url=self.guild.icon.url) return embed class ToggleButton(disnake.ui.Button): """Bouton de bascule avec indicateur de statut""" def __init__(self, label: str, emoji: str, is_enabled: bool, custom_id: str): style = disnake.ButtonStyle.green if is_enabled else disnake.ButtonStyle.red status = "✅" if is_enabled else "❌" super().__init__( label=f"{label} [{status}]", style=style, custom_id=custom_id, emoji=emoji ) async def callback(self, interaction: disnake.Interaction): view: SecurityView = self.view key = interaction.data['custom_id'] # Vérifier les permissions if not is_immune(view.guild, interaction.user): return await interaction.response.send_message( "❌ Permissions insuffisantes.", ephemeral=True ) # Basculer le paramètre view.settings[key] = not view.settings.get(key, False) save_settings(view.guild.id, view.settings) view.build_view() await interaction.response.edit_message(view=view) class MainCategorySelect(disnake.ui.Select): """Menu de sélection des catégories""" def __init__(self): options = [ disnake.SelectOption( label="Anti-Spam", emoji="🛡️", value="spam", description="Protection contre le spam et les liens" ), disnake.SelectOption( label="Anti-Raid", emoji="⚔️", value="raid", description="Protection contre les raids" ), disnake.SelectOption( label="Anti-Nuke", emoji="☢️", value="nuke", description="Protection contre la destruction du serveur" ), disnake.SelectOption( label="Logs", emoji="📋", value="logs", description="Journalisation des événements" ), disnake.SelectOption( label="Règlement", emoji="📜", value="rules", description="Créer et envoyer un règlement" ), disnake.SelectOption( label="Whitelist", emoji="👥", value="whitelist", description="Gérer les utilisateurs immunisés" ) ] super().__init__( placeholder="Sélectionnez une catégorie...", options=options, min_values=1, max_values=1 ) async def callback(self, interaction: disnake.Interaction): view: SecurityView = self.view view.category = self.values[0] view.build_view() # Créer l'embed de la catégorie category_embeds = { "spam": view.create_category_embed( "Configuration Anti-Spam", "Paramètres de protection contre le spam et les liens.\n\n" "• **Anti-Spam** : Détecte les messages répétitifs\n" "• **Anti-Liens** : Bloque les liens Discord et URLs" ), "raid": view.create_category_embed( "Configuration Anti-Raid", "Paramètres de protection contre les raids.\n\n" "• **Anti-Raid** : Détecte les vagues de join\n" "• **Auto-Ban** : Banne automatiquement les raiders" ), "nuke": view.create_category_embed( "Configuration Anti-Nuke", "Paramètres de protection contre la destruction.\n\n" "• **Anti-Salon** : Protège les salons\n" "• **Anti-Rôle** : Protège les rôles" ), "logs": view.create_category_embed( "Configuration Logs", "Paramètres de journalisation des événements.\n\n" "• **Logs Serveur** : Active la journalisation\n" "• **Channel** : Salon où seront envoyés les logs" ), "rules": view.create_category_embed( "Configuration Règlement", "Créez et envoyez un règlement pour votre serveur.\n\n" "• **Titre** : Personnalisez le titre du règlement\n" "• **Contenu** : Rédigez vos règles\n" "• **Salon** : Choisissez où l'envoyer\n" "• **Rôle** : Rôle attribué à l'acceptation\n\n" "Les membres devront accepter le règlement pour accéder au serveur." ), "whitelist": view.create_category_embed( "Gestion Whitelist", "Gérez les utilisateurs immunisés aux protections.\n\n" "Ces utilisateurs ne seront jamais sanctionnés." ) } embed = category_embeds.get(view.category, view.create_main_embed()) await interaction.response.edit_message(embed=embed, view=view) class WhitelistSelect(disnake.ui.Select): """Menu dropdown pour voir et gérer les utilisateurs whitelistés""" def __init__(self, guild: disnake.Guild, view: 'SecurityView'): self.guild = guild self.parent_view = view whitelist = load_whitelist(guild.id) options = [] for user_id in whitelist: # Essayer de trouver l'utilisateur member = guild.get_member(user_id) if member: label = f"{member.name} ({member.id})" emoji = "👤" else: label = f"ID: {user_id} (non trouvé)" emoji = "❓" options.append( disnake.SelectOption( label=label[:100], # Discord limite à 100 chars value=str(user_id), emoji=emoji, description="Cliquez pour retirer de la whitelist" ) ) super().__init__( placeholder="👥 Gérer la whitelist (cliquez pour retirer)...", options=options, min_values=1, max_values=1 ) async def callback(self, interaction: disnake.Interaction): """Gère la sélection d'un utilisateur à retirer""" user_id = int(self.values[0]) # Charger et modifier la whitelist data = {} if os.path.exists(WHITELIST_FILE): try: with open(WHITELIST_FILE, "r", encoding='utf-8') as f: data = json.load(f) except (json.JSONDecodeError, FileNotFoundError): pass guild_whitelist = data.get(str(self.guild.id), []) if user_id not in guild_whitelist: return await interaction.response.send_message( "❌ Utilisateur non trouvé dans la whitelist.", ephemeral=True ) # Retirer l'utilisateur guild_whitelist.remove(user_id) data[str(self.guild.id)] = guild_whitelist os.makedirs(os.path.dirname(WHITELIST_FILE), exist_ok=True) with open(WHITELIST_FILE, "w", encoding='utf-8') as f: json.dump(data, f, indent=4, ensure_ascii=False) # Mettre à jour l'affichage self.parent_view.whitelist_count = len(guild_whitelist) self.parent_view.build_view() await interaction.response.edit_message(view=self.parent_view) await interaction.followup.send( "✅ Utilisateur retiré de la whitelist.", ephemeral=True ) class Security(commands.Cog): """Cog principal de gestion de la sécurité""" def __init__(self, bot): self.bot = bot @commands.slash_command( name="security", description="🛡️ Ouvrir le panneau de sécurité Kuby" ) async def security(self, interaction: disnake.ApplicationCommandInteraction): """ Commande principale pour ouvrir l'interface de sécurité. Accessible uniquement aux administrateurs et propriétaires whitelistés. """ # On diffère la réponse immédiatement car le chargement des réglages peut prendre du temps await interaction.response.defer(ephemeral=True) # Vérification des permissions if not is_immune(interaction.guild, interaction.user): return await interaction.followup.send( "❌ **Accès refusé**\n\nVous devez être administrateur, propriétaire du serveur, ou être dans la whitelist pour accéder à ce panneau.", ephemeral=True ) try: # Chargement des paramètres settings = load_settings(interaction.guild.id) # Création de la vue view = SecurityView(interaction, settings, bot=self.bot) embed = view.create_main_embed(interaction.user) await interaction.followup.send( embed=embed, view=view, ephemeral=True ) except Exception as e: await interaction.followup.send( f"❌ Une erreur est survenue lors de l'ouverture du panneau : {e}", ephemeral=True ) @security.error async def security_error(self, interaction: disnake.Interaction, error: Exception): """Gestion des erreurs de la commande security""" # Utilisation de followup si l'interaction est déjà différée ou répondue send_method = interaction.followup.send if interaction.response.is_done() else interaction.response.send_message if isinstance(error, commands.MissingPermissions): await send_method( "❌ Permissions insuffisantes.", ephemeral=True ) else: await send_method( f"❌ Une erreur est survenue lors de l'exécution de la commande : {error}", ephemeral=True ) def setup(bot): """Setup du cog de sécurité""" bot.add_cog(Security(bot))