1051 lines
38 KiB
Python
Executable file
1051 lines
38 KiB
Python
Executable file
import disnake
|
||
from disnake.ext import commands
|
||
import json
|
||
import os
|
||
from typing import Optional
|
||
|
||
# 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()
|
||
|
||
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)
|
||
|
||
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"
|
||
)
|
||
]
|
||
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"
|
||
)
|
||
}
|
||
|
||
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))
|
||
|