diff --git a/commandes/security.py b/commandes/security.py index 9949653..de4aa4e 100755 --- a/commandes/security.py +++ b/commandes/security.py @@ -3,49 +3,84 @@ from discord import app_commands from discord.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): - if not os.path.exists(SETTINGS_FILE): return {} +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: return {} + except (json.JSONDecodeError, FileNotFoundError): + return {} -def save_settings(guild_id, settings): +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: pass + 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 is_whitelisted(guild_id, user_id): - if not os.path.exists(WHITELIST_FILE): return 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: return False + except (json.JSONDecodeError, FileNotFoundError): + return False -# --- MODAL DE RÉGLAGE DU SEUIL --- -class ThresholdModal(discord.ui.Modal, title="Réglage du Seuil Anti-Spam"): +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(discord.ui.Modal, title="🛡️ Seuil Anti-Spam"): + """Modal pour configurer le seuil anti-spam""" threshold_input = discord.ui.TextInput( - label="Messages max autorisés (en 5 secondes)", - placeholder="Par défaut : 5", + 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, settings, view): + + def __init__(self, guild_id: int, settings: dict, view: 'SecurityView'): super().__init__() self.guild_id = guild_id self.settings = settings @@ -53,109 +88,704 @@ class ThresholdModal(discord.ui.Modal, title="Réglage du Seuil Anti-Spam"): async def on_submit(self, interaction: discord.Interaction): if not self.threshold_input.value.isdigit(): - return await interaction.response.send_message("❌ Erreur : Veuillez entrer un nombre entier.", ephemeral=True) + return await interaction.response.send_message( + "❌ Erreur : Veuillez entrer un nombre entier.", + ephemeral=True + ) - new_val = int(self.threshold_input.value) - self.settings["spam_threshold"] = new_val + 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.send_message(f"✅ Le seuil a été réglé sur **{new_val}** messages.", ephemeral=True) + 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(discord.ui.Modal, title="⚔️ Seuil Anti-Raid"): + """Modal pour configurer le seuil anti-raid""" + threshold_input = discord.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__() + self.guild_id = guild_id + self.settings = settings + self.parent_view = view + + async def on_submit(self, interaction: discord.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 + ) + +class WhitelistModal(discord.ui.Modal, title="👤 Ajouter à la Whitelist"): + """Modal pour ajouter un utilisateur à la whitelist""" + user_input = discord.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__() + self.guild_id = guild_id + self.parent_view = view + + async def on_submit(self, interaction: discord.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 --- -# --- VUE DE GESTION --- class SecurityView(discord.ui.View): - def __init__(self, interaction, settings, category=None): - super().__init__(timeout=None) + """Vue principale du panneau de sécurité""" + + def __init__(self, interaction: discord.Interaction, settings: dict, category: Optional[str] = None): + super().__init__(timeout=300) # 5 minutes timeout self.interaction = interaction self.guild = interaction.guild self.settings = settings self.category = category + 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: - self.add_item(CategorySelect()) + # 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() + + 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 = discord.ui.Button( + label=f"🔢 Seuil: {threshold} msg/5s", + style=discord.ButtonStyle.blurple, + custom_id="spam_threshold" + ) + btn_threshold.callback = self.open_spam_threshold_modal + self.add_item(btn_threshold) + + # Bouton retour + btn_back = discord.ui.Button( + label="Retour", + style=discord.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 = discord.ui.Button( + label=f"🔢 Seuil: {threshold} join/10s", + style=discord.ButtonStyle.blurple, + custom_id="raid_threshold" + ) + btn_threshold.callback = self.open_raid_threshold_modal + self.add_item(btn_threshold) + + # Bouton retour + btn_back = discord.ui.Button( + label="Retour", + style=discord.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 = discord.ui.Button( + label="Retour", + style=discord.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 = discord.ui.Button( + label=f"📌 Channel: {channel.name if channel else 'Invalide'}", + style=discord.ButtonStyle.green, + disabled=True + ) else: - config_map = { - "raid": [("anti_raid", "🛡️ Anti-Raid"), ("auto_ban", "🔨 Auto-Ban")], - "spam": [("anti_spam", "🛡️ Anti-Spam"), ("anti_link", "🔗 Anti-Link"), ("anti_mention", "👥 Anti-Mention")], - "nuke": [("anti_channel_delete", "📁 Anti-Salon"), ("anti_role_create", "🎭 Anti-Rôle")], - "logs": [("server_logs", "📋 Activer Logs")] - } + btn_channel = discord.ui.Button( + label="📌 Aucun channel configuré", + style=discord.ButtonStyle.red, + disabled=True + ) + self.add_item(btn_channel) + + # Bouton retour + btn_back = discord.ui.Button( + label="Retour", + style=discord.ButtonStyle.gray, + emoji="⬅️" + ) + btn_back.callback = self.back_to_main + self.add_item(btn_back) - for key, label in config_map.get(self.category, []): - is_on = self.settings.get(key, False) - style = discord.ButtonStyle.green if is_on else discord.ButtonStyle.red - status = "✅" if is_on else "❌" - btn = discord.ui.Button(label=f"{label} [{status}]", style=style, custom_id=key) - btn.callback = self.toggle_setting - self.add_item(btn) - - # BOUTON SPÉCIAL : RÉGLAGE SEUIL (uniquement dans spam) - if self.category == "spam": - btn_threshold = discord.ui.Button(label=f"🔢 Seuil: {self.settings.get('spam_threshold', 5)}", style=discord.ButtonStyle.blurple) - btn_threshold.callback = self.open_threshold_modal - self.add_item(btn_threshold) + def build_whitelist_view(self): + """Construit la vue Whitelist""" + # Affichage du nombre avec style + btn_count = discord.ui.Button( + label=f"👥 Whitelist: {self.whitelist_count} utilisateur(s)", + style=discord.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 = discord.ui.Button( + label="📭 Aucun utilisateur dans la whitelist", + style=discord.ButtonStyle.gray, + disabled=True + ) + self.add_item(btn_empty) + + # Bouton ajouter + btn_add = discord.ui.Button( + label="➕ Ajouter", + style=discord.ButtonStyle.green, + emoji="➕", + custom_id="whitelist_add" + ) + btn_add.callback = self.open_whitelist_modal + self.add_item(btn_add) + + # Bouton retour + btn_back = discord.ui.Button( + label="Retour", + style=discord.ButtonStyle.gray, + emoji="⬅️" + ) + btn_back.callback = self.back_to_main + self.add_item(btn_back) - back_btn = discord.ui.Button(label="⬅️ Retour", style=discord.ButtonStyle.gray) - back_btn.callback = self.back_to_main - self.add_item(back_btn) - - async def toggle_setting(self, interaction: discord.Interaction): - key = interaction.data['custom_id'] + async def toggle_setting(self, interaction: discord.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_threshold_modal(self, interaction: discord.Interaction): - await interaction.response.send_modal(ThresholdModal(self.guild.id, self.settings, self)) + async def open_spam_threshold_modal(self, interaction: discord.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: discord.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: discord.Interaction): + """Ouvre le modal d'ajout à la whitelist""" + await interaction.response.send_modal( + WhitelistModal(self.guild.id, self) + ) async def back_to_main(self, interaction: discord.Interaction): + """Retour au menu principal""" self.category = None self.build_view() - embed = self.create_main_embed() + embed = self.create_main_embed(interaction.user) await interaction.response.edit_message(embed=embed, view=self) - def create_main_embed(self): - embed = discord.Embed(title="🛡️ Centre de Sécurité Kuby", description="Interface modulaire de protection.", color=0x2b2d31) - if self.guild.icon: embed.set_thumbnail(url=self.guild.icon.url) - embed.add_field(name="Propriétaire", value=self.guild.owner.mention) + def create_main_embed(self, user: discord.Member = None) -> discord.Embed: + """Crée l'embed du menu principal""" + embed = discord.Embed( + title="🛡️ Centre de Sécurité Kuby", + description="Gérez la protection de votre serveur", + color=0x2b2d31 + ) + + 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 -class CategorySelect(discord.ui.Select): + def create_category_embed(self, title: str, description: str) -> discord.Embed: + """Crée un embed pour une catégorie""" + embed = discord.Embed( + title=title, + description=description, + color=0x2b2d31 + ) + + if self.guild.icon: + embed.set_thumbnail(url=self.guild.icon.url) + + return embed + + +class ToggleButton(discord.ui.Button): + """Bouton de bascule avec indicateur de statut""" + + def __init__(self, label: str, emoji: str, is_enabled: bool, custom_id: str): + style = discord.ButtonStyle.green if is_enabled else discord.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: discord.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(discord.ui.Select): + """Menu de sélection des catégories""" + def __init__(self): options = [ - discord.SelectOption(label="Anti-Raid", value="raid", emoji="⚔️"), - discord.SelectOption(label="Anti-Spam", value="spam", emoji="🛡️"), - discord.SelectOption(label="Anti-Nuke", value="nuke", emoji="☢️"), - discord.SelectOption(label="Logs", value="logs", emoji="📋") + discord.SelectOption( + label="Anti-Spam", + emoji="🛡️", + value="spam", + description="Protection contre le spam et les liens" + ), + discord.SelectOption( + label="Anti-Raid", + emoji="⚔️", + value="raid", + description="Protection contre les raids" + ), + discord.SelectOption( + label="Anti-Nuke", + emoji="☢️", + value="nuke", + description="Protection contre la destruction du serveur" + ), + discord.SelectOption( + label="Logs", + emoji="📋", + value="logs", + description="Journalisation des événements" + ), + discord.SelectOption( + label="Whitelist", + emoji="👥", + value="whitelist", + description="Gérer les utilisateurs immunisés" + ) ] - super().__init__(placeholder="Sélectionnez un module...", options=options) + super().__init__( + placeholder="Sélectionnez une catégorie...", + options=options, + min_values=1, + max_values=1 + ) async def callback(self, interaction: discord.Interaction): view: SecurityView = self.view view.category = self.values[0] view.build_view() - embed = discord.Embed(title=f"⚙️ Configuration : {view.category.upper()}", color=0x2b2d31) + + # 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" + ), + "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) -# --- COG --- + +class WhitelistSelect(discord.ui.Select): + """Menu dropdown pour voir et gérer les utilisateurs whitelistés""" + + def __init__(self, guild: discord.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( + discord.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: discord.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 - @app_commands.command(name="security", description="Ouvrir le panneau Kuby") + @app_commands.command( + name="security", + description="🛡️ Ouvrir le panneau de sécurité Kuby" + ) async def security(self, interaction: discord.Interaction): - if not (interaction.user.guild_permissions.administrator or - interaction.user.id == interaction.guild.owner_id or - is_whitelisted(interaction.guild.id, interaction.user.id)): - return await interaction.response.send_message("❌ Accès refusé.", ephemeral=True) + """ + Commande principale pour ouvrir l'interface de sécurité. + Accessible uniquement aux administrateurs et propriétaires whitelistés. + """ + # Vérification des permissions + if not is_immune(interaction.guild, interaction.user): + if interaction.response.is_done(): + 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 + ) + return await interaction.response.send_message( + "❌ **Accès refusé**\n\nVous devez être administrateur, propriétaire du serveur, ou être dans la whitelist pour accéder à ce panneau.", + ephemeral=True + ) + # Chargement des paramètres settings = load_settings(interaction.guild.id) + + # Création de la vue view = SecurityView(interaction, settings) - await interaction.response.send_message(embed=view.create_main_embed(), view=view) + embed = view.create_main_embed(interaction.user) + + if interaction.response.is_done(): + await interaction.followup.send( + embed=embed, + view=view, + ephemeral=True + ) + else: + await interaction.response.send_message( + embed=embed, + view=view, + ephemeral=True + ) + + @security.error + async def security_error(self, interaction: discord.Interaction, error: app_commands.AppCommandError): + """Gestion des erreurs de la commande security""" + # Ne pas envoyer de message si l'interaction a déjà été traitée + if interaction.response.is_done(): + return + + if isinstance(error, app_commands.MissingPermissions): + await interaction.response.send_message( + "❌ Permissions insuffisantes.", + ephemeral=True + ) + else: + await interaction.response.send_message( + f"❌ Une erreur est survenue.", + ephemeral=True + ) + async def setup(bot): - await bot.add_cog(Security(bot)) \ No newline at end of file + """Setup du cog de sécurité""" + await bot.add_cog(Security(bot)) + diff --git a/data/logs/messages_2026-01-02.json b/data/logs/messages_2026-01-02.json new file mode 100644 index 0000000..b4bebd2 --- /dev/null +++ b/data/logs/messages_2026-01-02.json @@ -0,0 +1,59 @@ +[ + { + "timestamp": "2026-01-02T13:30:27.725564", + "event_type": "deleted", + "message_id": 1456625676537823243, + "channel_id": 1369669999677145098, + "channel_name": "╭💬・général", + "author_id": 971446412690722826, + "author_name": "gameurpro12", + "author_roles": [ + 1369669999345537145, + 1369669999366770723, + 1403041729330024508, + 1369669999366770726 + ], + "content": "gg", + "attachments": [], + "embeds": [], + "details": {} + }, + { + "timestamp": "2026-01-02T13:30:27.754495", + "event_type": "deleted", + "message_id": 1456625676537823243, + "channel_id": 1369669999677145098, + "channel_name": "╭💬・général", + "author_id": 971446412690722826, + "author_name": "gameurpro12", + "author_roles": [ + 1369669999345537145, + 1369669999366770723, + 1403041729330024508, + 1369669999366770726 + ], + "content": "gg", + "attachments": [], + "embeds": [], + "details": {} + }, + { + "timestamp": "2026-01-02T13:30:37.215876", + "event_type": "deleted", + "message_id": 1456625680153444448, + "channel_id": 1369669999677145098, + "channel_name": "╭💬・général", + "author_id": 971446412690722826, + "author_name": "gameurpro12", + "author_roles": [ + 1369669999345537145, + 1369669999366770723, + 1403041729330024508, + 1369669999366770726 + ], + "content": "gg", + "attachments": [], + "embeds": [], + "details": {} + } +] \ No newline at end of file