Kuby/commandes/security.py

1089 lines
39 KiB
Python
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import disnake
from disnake.ext import commands
import json
import os
from typing import Optional
from utils.premium import check_premium_tier
# Chemins ABSOLUS des fichiers (basés sur le fichier courant)
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(BASE_DIR, "..", "data")
SETTINGS_FILE = os.path.join(DATA_DIR, "security_settings.json")
WHITELIST_FILE = os.path.join(DATA_DIR, "whitelist.json")
# Créer le dossier data/ s'il n'existe pas
os.makedirs(DATA_DIR, exist_ok=True)
# --- 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(DATA_DIR, 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()
elif self.category == "membres":
self.build_members_view()
elif self.category == "taches":
self.build_taches_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
)
def build_members_view(self):
"""Construit la vue Paramètres Membres"""
# Toggle Restauration des rôles
role_restore = self.settings.get("role_restore_enabled", True)
btn_restore = ToggleButton(
label="Restauration des rôles",
emoji="🔄",
is_enabled=role_restore,
custom_id="role_restore_enabled"
)
self.add_item(btn_restore)
# 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_taches_view(self):
"""Construit la vue Gestion des Tâches"""
# Toggle Activer/Désactiver
taches_enabled = self.settings.get("taches_enabled", True)
btn_taches = ToggleButton(
label="Module Tâches",
emoji="📌",
is_enabled=taches_enabled,
custom_id="taches_enabled"
)
self.add_item(btn_taches)
# 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 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"
),
disnake.SelectOption(
label="Membres",
emoji="👤",
value="membres",
description="Paramètres liés aux membres"
),
disnake.SelectOption(
label="Tâches",
emoji="📌",
value="taches",
description="Gestion des tâches et rappels"
)
]
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."
),
"membres": view.create_category_embed(
"Paramètres Membres",
"Paramètres liés à la gestion des membres.\n\n"
"• **Restauration des rôles** : Restaurer automatiquement les rôles des membres de retour"
),
"taches": view.create_category_embed(
"Gestion des Tâches",
"Configurez le système de gestion des tâches.\n\n"
"• **Activer/Désactiver** : Active ou désactive le module sur ce serveur\n"
"• **Création** : Seuls les administrateurs peuvent créer des tâches\n"
"• **Rappels** : Notifications automatiques 1h avant l'échéance"
)
}
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"
)
@check_premium_tier()
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))