Kuby/commandes/security.py

1013 lines
36 KiB
Python
Raw Normal View History

2026-05-06 17:07:09 +02:00
import disnake
from disnake.ext import commands
2026-01-01 18:48:25 +01:00
import json
import os
2026-01-02 19:04:05 +01:00
from typing import Optional
2026-01-01 18:48:25 +01:00
# Chemins des fichiers
SETTINGS_FILE = "data/security_settings.json"
WHITELIST_FILE = "data/whitelist.json"
# --- UTILITAIRES ---
2026-01-02 19:04:05 +01:00
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 {}
2026-01-01 18:48:25 +01:00
try:
with open(SETTINGS_FILE, "r", encoding='utf-8') as f:
return json.load(f).get(str(guild_id), {})
2026-01-02 19:04:05 +01:00
except (json.JSONDecodeError, FileNotFoundError):
return {}
2026-01-01 18:48:25 +01:00
2026-01-02 19:04:05 +01:00
def save_settings(guild_id: int, settings: dict) -> None:
"""Sauvegarde les paramètres de sécurité"""
2026-01-01 18:48:25 +01:00
data = {}
if os.path.exists(SETTINGS_FILE):
try:
2026-01-02 19:04:05 +01:00
with open(SETTINGS_FILE, "r", encoding='utf-8') as f:
data = json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
pass
2026-01-01 18:48:25 +01:00
data[str(guild_id)] = settings
2026-01-02 19:04:05 +01:00
2026-01-01 18:48:25 +01:00
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)
2026-01-02 19:04:05 +01:00
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
2026-01-01 18:48:25 +01:00
try:
with open(WHITELIST_FILE, "r", encoding='utf-8') as f:
data = json.load(f)
return user_id in data.get(str(guild_id), [])
2026-01-02 19:04:05 +01:00
except (json.JSONDecodeError, FileNotFoundError):
return False
2026-01-01 18:48:25 +01:00
2026-01-02 19:04:05 +01:00
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 ---
2026-05-06 17:07:09 +02:00
class SpamThresholdModal(disnake.ui.Modal, title="🛡️ Seuil Anti-Spam"):
2026-01-02 19:04:05 +01:00
"""Modal pour configurer le seuil anti-spam"""
2026-05-06 17:07:09 +02:00
threshold_input = disnake.ui.TextInput(
2026-01-02 19:04:05 +01:00
label="Messages max autorisés",
placeholder="Entre 3 et 10 (défaut: 5)",
2026-01-01 18:48:25 +01:00
min_length=1,
max_length=2,
required=True
)
2026-01-02 19:04:05 +01:00
def __init__(self, guild_id: int, settings: dict, view: 'SecurityView'):
super().__init__()
self.guild_id = guild_id
self.settings = settings
self.parent_view = view
2026-05-06 17:07:09 +02:00
async def on_submit(self, interaction: disnake.Interaction):
2026-01-02 19:04:05 +01:00
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
)
2026-01-01 18:48:25 +01:00
2026-05-06 17:07:09 +02:00
class RaidThresholdModal(disnake.ui.Modal, title="⚔️ Seuil Anti-Raid"):
2026-01-02 19:04:05 +01:00
"""Modal pour configurer le seuil anti-raid"""
2026-05-06 17:07:09 +02:00
threshold_input = disnake.ui.TextInput(
2026-01-02 19:04:05 +01:00
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'):
2026-01-01 18:48:25 +01:00
super().__init__()
self.guild_id = guild_id
self.settings = settings
self.parent_view = view
2026-05-06 17:07:09 +02:00
async def on_submit(self, interaction: disnake.Interaction):
2026-01-01 18:48:25 +01:00
if not self.threshold_input.value.isdigit():
2026-01-02 19:04:05 +01:00
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
)
2026-01-01 18:48:25 +01:00
2026-01-02 19:04:05 +01:00
self.settings["raid_threshold"] = value
2026-01-01 18:48:25 +01:00
save_settings(self.guild_id, self.settings)
2026-01-02 19:04:05 +01:00
self.parent_view.settings = self.settings
self.parent_view.build_view()
2026-01-01 18:48:25 +01:00
2026-01-02 19:04:05 +01:00
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 ---
2026-05-06 17:07:09 +02:00
class WhitelistModal(disnake.ui.Modal, title="👤 Ajouter à la Whitelist"):
2026-01-02 19:04:05 +01:00
"""Modal pour ajouter un utilisateur à la whitelist"""
2026-05-06 17:07:09 +02:00
user_input = disnake.ui.TextInput(
2026-01-02 19:04:05 +01:00
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
2026-05-06 17:07:09 +02:00
async def on_submit(self, interaction: disnake.Interaction):
2026-01-02 19:04:05 +01:00
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 ---
2026-01-01 18:48:25 +01:00
2026-05-06 17:07:09 +02:00
class SecurityView(disnake.ui.View):
2026-01-02 19:04:05 +01:00
"""Vue principale du panneau de sécurité"""
2026-05-06 17:07:09 +02:00
def __init__(self, interaction: disnake.Interaction, settings: dict, category: Optional[str] = None, bot=None):
2026-01-02 19:04:05 +01:00
super().__init__(timeout=300) # 5 minutes timeout
2026-01-01 18:48:25 +01:00
self.interaction = interaction
self.guild = interaction.guild
self.settings = settings
self.category = category
self.bot = bot
2026-01-02 19:04:05 +01:00
self.whitelist_count = len(load_whitelist(self.guild.id))
2026-01-01 18:48:25 +01:00
self.build_view()
def build_view(self):
2026-01-02 19:04:05 +01:00
"""Construit les boutons selon la catégorie"""
2026-01-01 18:48:25 +01:00
self.clear_items()
if self.category is None:
2026-01-02 19:04:05 +01:00
# 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()
2026-01-02 19:04:05 +01:00
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)
2026-05-06 17:07:09 +02:00
btn_threshold = disnake.ui.Button(
2026-01-02 19:04:05 +01:00
label=f"🔢 Seuil: {threshold} msg/5s",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.blurple,
2026-01-02 19:04:05 +01:00
custom_id="spam_threshold"
)
btn_threshold.callback = self.open_spam_threshold_modal
self.add_item(btn_threshold)
# Bouton retour
2026-05-06 17:07:09 +02:00
btn_back = disnake.ui.Button(
2026-01-02 19:04:05 +01:00
label="Retour",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.gray,
2026-01-02 19:04:05 +01:00
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)
2026-05-06 17:07:09 +02:00
btn_threshold = disnake.ui.Button(
2026-01-02 19:04:05 +01:00
label=f"🔢 Seuil: {threshold} join/10s",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.blurple,
2026-01-02 19:04:05 +01:00
custom_id="raid_threshold"
)
btn_threshold.callback = self.open_raid_threshold_modal
self.add_item(btn_threshold)
# Bouton retour
2026-05-06 17:07:09 +02:00
btn_back = disnake.ui.Button(
2026-01-02 19:04:05 +01:00
label="Retour",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.gray,
2026-01-02 19:04:05 +01:00
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
2026-05-06 17:07:09 +02:00
btn_back = disnake.ui.Button(
2026-01-02 19:04:05 +01:00
label="Retour",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.gray,
2026-01-02 19:04:05 +01:00
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}`"
2026-05-06 17:07:09 +02:00
btn_channel = disnake.ui.Button(
2026-01-02 19:04:05 +01:00
label=f"📌 Channel: {channel.name if channel else 'Invalide'}",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.green,
2026-01-02 19:04:05 +01:00
disabled=True
)
2026-01-01 18:48:25 +01:00
else:
2026-05-06 17:07:09 +02:00
btn_channel = disnake.ui.Button(
2026-01-02 19:04:05 +01:00
label="📌 Aucun channel configuré",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.red,
2026-01-02 19:04:05 +01:00
disabled=True
)
self.add_item(btn_channel)
# Bouton retour
2026-05-06 17:07:09 +02:00
btn_back = disnake.ui.Button(
2026-01-02 19:04:05 +01:00
label="Retour",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.gray,
2026-01-02 19:04:05 +01:00
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
2026-05-06 17:07:09 +02:00
btn_count = disnake.ui.Button(
2026-01-02 19:04:05 +01:00
label=f"👥 Whitelist: {self.whitelist_count} utilisateur(s)",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.blurple,
2026-01-02 19:04:05 +01:00
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
2026-05-06 17:07:09 +02:00
btn_empty = disnake.ui.Button(
2026-01-02 19:04:05 +01:00
label="📭 Aucun utilisateur dans la whitelist",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.gray,
2026-01-02 19:04:05 +01:00
disabled=True
)
self.add_item(btn_empty)
# Bouton ajouter
2026-05-06 17:07:09 +02:00
btn_add = disnake.ui.Button(
2026-01-02 19:04:05 +01:00
label=" Ajouter",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.green,
2026-01-02 19:04:05 +01:00
emoji="",
custom_id="whitelist_add"
)
btn_add.callback = self.open_whitelist_modal
self.add_item(btn_add)
# Bouton retour
2026-05-06 17:07:09 +02:00
btn_back = disnake.ui.Button(
2026-01-02 19:04:05 +01:00
label="Retour",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.gray,
2026-01-02 19:04:05 +01:00
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
2026-05-06 17:07:09 +02:00
btn_title = disnake.ui.Button(
label="Éditer le titre",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.blurple,
emoji="📝"
)
btn_title.callback = self.open_rules_title_modal
self.add_item(btn_title)
# Bouton pour modifier le contenu
2026-05-06 17:07:09 +02:00
btn_content = disnake.ui.Button(
label="Éditer le contenu",
2026-05-06 17:07:09 +02:00
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))
2026-05-06 17:07:09 +02:00
btn_channel = disnake.ui.Button(
label=f"Salon: {channel.name if channel else 'Invalide'}",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.green,
custom_id="rules_channel"
)
else:
2026-05-06 17:07:09 +02:00
btn_channel = disnake.ui.Button(
label="Aucun salon configuré",
2026-05-06 17:07:09 +02:00
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))
2026-05-06 17:07:09 +02:00
btn_role = disnake.ui.Button(
label=f"Rôle: {role.name if role else 'Invalide'}",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.green,
custom_id="rules_role"
)
else:
2026-05-06 17:07:09 +02:00
btn_role = disnake.ui.Button(
label="Aucun rôle configuré",
2026-05-06 17:07:09 +02:00
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"
2026-05-06 17:07:09 +02:00
btn_message_type = disnake.ui.Button(
label=f"Type: {type_label}",
2026-05-06 17:07:09 +02:00
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
2026-05-06 17:07:09 +02:00
btn_send = disnake.ui.Button(
label="Envoyer le règlement",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.green,
emoji="🚀"
)
btn_send.callback = self.send_rules
self.add_item(btn_send)
# Bouton retour
2026-05-06 17:07:09 +02:00
btn_back = disnake.ui.Button(
label="Retour",
2026-05-06 17:07:09 +02:00
style=disnake.ButtonStyle.gray,
emoji="⬅️"
)
btn_back.callback = self.back_to_main
self.add_item(btn_back)
2026-05-06 17:07:09 +02:00
async def toggle_setting(self, interaction: disnake.Interaction, key: str):
2026-01-02 19:04:05 +01:00
"""Bascule un paramètre"""
2026-01-01 18:48:25 +01:00
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)
2026-05-06 17:07:09 +02:00
async def open_spam_threshold_modal(self, interaction: disnake.Interaction):
2026-01-02 19:04:05 +01:00
"""Ouvre le modal de configuration du seuil spam"""
await interaction.response.send_modal(
SpamThresholdModal(self.guild.id, self.settings, self)
)
2026-05-06 17:07:09 +02:00
async def open_raid_threshold_modal(self, interaction: disnake.Interaction):
2026-01-02 19:04:05 +01:00
"""Ouvre le modal de configuration du seuil raid"""
await interaction.response.send_modal(
RaidThresholdModal(self.guild.id, self.settings, self)
)
2026-05-06 17:07:09 +02:00
async def open_whitelist_modal(self, interaction: disnake.Interaction):
2026-01-02 19:04:05 +01:00
"""Ouvre le modal d'ajout à la whitelist"""
await interaction.response.send_modal(
WhitelistModal(self.guild.id, self)
)
2026-01-01 18:48:25 +01:00
2026-05-06 17:07:09 +02:00
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)
)
2026-05-06 17:07:09 +02:00
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)
)
2026-05-06 17:07:09 +02:00
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)
)
2026-05-06 17:07:09 +02:00
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)
)
2026-05-06 17:07:09 +02:00
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)
)
2026-05-06 17:07:09 +02:00
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
)
2026-05-06 17:07:09 +02:00
async def back_to_main(self, interaction: disnake.Interaction):
2026-01-02 19:04:05 +01:00
"""Retour au menu principal"""
2026-01-01 18:48:25 +01:00
self.category = None
self.build_view()
2026-01-02 19:04:05 +01:00
embed = self.create_main_embed(interaction.user)
2026-01-01 18:48:25 +01:00
await interaction.response.edit_message(embed=embed, view=self)
2026-05-06 17:07:09 +02:00
def create_main_embed(self, user: disnake.Member = None) -> disnake.Embed:
2026-01-02 19:04:05 +01:00
"""Crée l'embed du menu principal"""
2026-05-06 17:07:09 +02:00
embed = disnake.Embed(
2026-01-02 19:04:05 +01:00
title="🛡️ Centre de Sécurité Kuby",
description="Gérez la protection de votre serveur",
color=0x7289DA
2026-01-02 19:04:05 +01:00
)
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
2026-05-06 17:07:09 +02:00
def create_category_embed(self, title: str, description: str) -> disnake.Embed:
2026-01-02 19:04:05 +01:00
"""Crée un embed pour une catégorie"""
2026-05-06 17:07:09 +02:00
embed = disnake.Embed(
2026-01-02 19:04:05 +01:00
title=title,
description=description,
color=0x7289DA
2026-01-02 19:04:05 +01:00
)
if self.guild.icon:
embed.set_thumbnail(url=self.guild.icon.url)
2026-01-01 18:48:25 +01:00
return embed
2026-01-02 19:04:05 +01:00
2026-05-06 17:07:09 +02:00
class ToggleButton(disnake.ui.Button):
2026-01-02 19:04:05 +01:00
"""Bouton de bascule avec indicateur de statut"""
def __init__(self, label: str, emoji: str, is_enabled: bool, custom_id: str):
2026-05-06 17:07:09 +02:00
style = disnake.ButtonStyle.green if is_enabled else disnake.ButtonStyle.red
2026-01-02 19:04:05 +01:00
status = "" if is_enabled else ""
super().__init__(
label=f"{label} [{status}]",
style=style,
custom_id=custom_id,
emoji=emoji
)
2026-05-06 17:07:09 +02:00
async def callback(self, interaction: disnake.Interaction):
2026-01-02 19:04:05 +01:00
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)
2026-05-06 17:07:09 +02:00
class MainCategorySelect(disnake.ui.Select):
2026-01-02 19:04:05 +01:00
"""Menu de sélection des catégories"""
2026-01-01 18:48:25 +01:00
def __init__(self):
options = [
2026-05-06 17:07:09 +02:00
disnake.SelectOption(
2026-01-02 19:04:05 +01:00
label="Anti-Spam",
emoji="🛡️",
value="spam",
description="Protection contre le spam et les liens"
),
2026-05-06 17:07:09 +02:00
disnake.SelectOption(
2026-01-02 19:04:05 +01:00
label="Anti-Raid",
emoji="⚔️",
value="raid",
description="Protection contre les raids"
),
2026-05-06 17:07:09 +02:00
disnake.SelectOption(
2026-01-02 19:04:05 +01:00
label="Anti-Nuke",
emoji="☢️",
value="nuke",
description="Protection contre la destruction du serveur"
),
2026-05-06 17:07:09 +02:00
disnake.SelectOption(
2026-01-02 19:04:05 +01:00
label="Logs",
emoji="📋",
value="logs",
description="Journalisation des événements"
),
2026-05-06 17:07:09 +02:00
disnake.SelectOption(
label="Règlement",
emoji="📜",
value="rules",
description="Créer et envoyer un règlement"
),
2026-05-06 17:07:09 +02:00
disnake.SelectOption(
2026-01-02 19:04:05 +01:00
label="Whitelist",
emoji="👥",
value="whitelist",
description="Gérer les utilisateurs immunisés"
)
2026-01-01 18:48:25 +01:00
]
2026-01-02 19:04:05 +01:00
super().__init__(
placeholder="Sélectionnez une catégorie...",
options=options,
min_values=1,
max_values=1
)
2026-01-01 18:48:25 +01:00
2026-05-06 17:07:09 +02:00
async def callback(self, interaction: disnake.Interaction):
2026-01-01 18:48:25 +01:00
view: SecurityView = self.view
view.category = self.values[0]
view.build_view()
2026-01-02 19:04:05 +01:00
# 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."
),
2026-01-02 19:04:05 +01:00
"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())
2026-01-01 18:48:25 +01:00
await interaction.response.edit_message(embed=embed, view=view)
2026-01-02 19:04:05 +01:00
2026-05-06 17:07:09 +02:00
class WhitelistSelect(disnake.ui.Select):
2026-01-02 19:04:05 +01:00
"""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(
2026-05-06 17:07:09 +02:00
disnake.SelectOption(
2026-01-02 19:04:05 +01:00
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
)
2026-05-06 17:07:09 +02:00
async def callback(self, interaction: disnake.Interaction):
2026-01-02 19:04:05 +01:00
"""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
)
2026-01-01 18:48:25 +01:00
class Security(commands.Cog):
2026-01-02 19:04:05 +01:00
"""Cog principal de gestion de la sécurité"""
2026-01-01 18:48:25 +01:00
def __init__(self, bot):
self.bot = bot
2026-01-02 19:04:05 +01:00
@app_commands.command(
name="security",
description="🛡️ Ouvrir le panneau de sécurité Kuby"
)
2026-05-06 17:07:09 +02:00
async def security(self, interaction: disnake.Interaction):
2026-01-02 19:04:05 +01:00
"""
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)
2026-01-02 19:04:05 +01:00
# Vérification des permissions
if not is_immune(interaction.guild, interaction.user):
return await interaction.followup.send(
2026-01-02 19:04:05 +01:00
"❌ **Accès refusé**\n\nVous devez être administrateur, propriétaire du serveur, ou être dans la whitelist pour accéder à ce panneau.",
ephemeral=True
)
2026-01-01 18:48:25 +01:00
try:
# Chargement des paramètres
settings = load_settings(interaction.guild.id)
2026-01-02 19:04:05 +01:00
# Création de la vue
view = SecurityView(interaction, settings, bot=self.bot)
embed = view.create_main_embed(interaction.user)
2026-01-02 19:04:05 +01:00
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}",
2026-01-02 19:04:05 +01:00
ephemeral=True
)
@security.error
2026-05-06 17:07:09 +02:00
async def security_error(self, interaction: disnake.Interaction, error: app_commands.AppCommandError):
2026-01-02 19:04:05 +01:00
"""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
2026-01-02 19:04:05 +01:00
if isinstance(error, app_commands.MissingPermissions):
await send_method(
2026-01-02 19:04:05 +01:00
"❌ Permissions insuffisantes.",
ephemeral=True
)
else:
await send_method(
f"❌ Une erreur est survenue lors de l'exécution de la commande : {error}",
2026-01-02 19:04:05 +01:00
ephemeral=True
)
2026-01-01 18:48:25 +01:00
async def setup(bot):
2026-01-02 19:04:05 +01:00
"""Setup du cog de sécurité"""
await bot.add_cog(Security(bot))