Refonte § Debug du menu principal
This commit is contained in:
parent
343a4f879c
commit
fd2d2a74dc
2 changed files with 759 additions and 70 deletions
|
|
@ -3,49 +3,84 @@ from discord import app_commands
|
|||
from discord.ext import commands
|
||||
import json
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
# Chemins des fichiers
|
||||
SETTINGS_FILE = "data/security_settings.json"
|
||||
WHITELIST_FILE = "data/whitelist.json"
|
||||
|
||||
# --- UTILITAIRES ---
|
||||
def load_settings(guild_id):
|
||||
if not os.path.exists(SETTINGS_FILE): return {}
|
||||
def load_settings(guild_id: int) -> dict:
|
||||
"""Charge les paramètres de sécurité pour un serveur"""
|
||||
if not os.path.exists(SETTINGS_FILE):
|
||||
return {}
|
||||
try:
|
||||
with open(SETTINGS_FILE, "r", encoding='utf-8') as f:
|
||||
return json.load(f).get(str(guild_id), {})
|
||||
except: return {}
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
return {}
|
||||
|
||||
def save_settings(guild_id, settings):
|
||||
def save_settings(guild_id: int, settings: dict) -> None:
|
||||
"""Sauvegarde les paramètres de sécurité"""
|
||||
data = {}
|
||||
if os.path.exists(SETTINGS_FILE):
|
||||
try:
|
||||
with open(SETTINGS_FILE, "r", encoding='utf-8') as f: data = json.load(f)
|
||||
except: pass
|
||||
with open(SETTINGS_FILE, "r", encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
data[str(guild_id)] = settings
|
||||
|
||||
os.makedirs(os.path.dirname(SETTINGS_FILE), exist_ok=True)
|
||||
with open(SETTINGS_FILE, "w", encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
|
||||
def is_whitelisted(guild_id, user_id):
|
||||
if not os.path.exists(WHITELIST_FILE): return False
|
||||
def load_whitelist(guild_id: int) -> list:
|
||||
"""Charge la whitelist pour un serveur"""
|
||||
if not os.path.exists(WHITELIST_FILE):
|
||||
return []
|
||||
try:
|
||||
with open(WHITELIST_FILE, "r", encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return data.get(str(guild_id), [])
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
return []
|
||||
|
||||
def is_whitelisted(guild_id: int, user_id: int) -> bool:
|
||||
"""Vérifie si un utilisateur est whitelisté"""
|
||||
if not os.path.exists(WHITELIST_FILE):
|
||||
return False
|
||||
try:
|
||||
with open(WHITELIST_FILE, "r", encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
return user_id in data.get(str(guild_id), [])
|
||||
except: return False
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
return False
|
||||
|
||||
# --- MODAL DE RÉGLAGE DU SEUIL ---
|
||||
class ThresholdModal(discord.ui.Modal, title="Réglage du Seuil Anti-Spam"):
|
||||
def is_immune(guild, user) -> bool:
|
||||
"""Vérifie l'immunité (propriétaire, admin, ou whitelist)"""
|
||||
if user.id == guild.owner_id:
|
||||
return True
|
||||
if user.guild_permissions.administrator:
|
||||
return True
|
||||
if is_whitelisted(guild.id, user.id):
|
||||
return True
|
||||
return False
|
||||
|
||||
# --- MODALS DE CONFIGURATION ---
|
||||
|
||||
class SpamThresholdModal(discord.ui.Modal, title="🛡️ Seuil Anti-Spam"):
|
||||
"""Modal pour configurer le seuil anti-spam"""
|
||||
threshold_input = discord.ui.TextInput(
|
||||
label="Messages max autorisés (en 5 secondes)",
|
||||
placeholder="Par défaut : 5",
|
||||
label="Messages max autorisés",
|
||||
placeholder="Entre 3 et 10 (défaut: 5)",
|
||||
min_length=1,
|
||||
max_length=2,
|
||||
required=True
|
||||
)
|
||||
|
||||
def __init__(self, guild_id, settings, view):
|
||||
def __init__(self, guild_id: int, settings: dict, view: 'SecurityView'):
|
||||
super().__init__()
|
||||
self.guild_id = guild_id
|
||||
self.settings = settings
|
||||
|
|
@ -53,109 +88,704 @@ class ThresholdModal(discord.ui.Modal, title="Réglage du Seuil Anti-Spam"):
|
|||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
if not self.threshold_input.value.isdigit():
|
||||
return await interaction.response.send_message("❌ Erreur : Veuillez entrer un nombre entier.", ephemeral=True)
|
||||
return await interaction.response.send_message(
|
||||
"❌ Erreur : Veuillez entrer un nombre entier.",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
new_val = int(self.threshold_input.value)
|
||||
self.settings["spam_threshold"] = new_val
|
||||
value = int(self.threshold_input.value)
|
||||
if value < 1 or value > 10:
|
||||
return await interaction.response.send_message(
|
||||
"❌ Erreur : La valeur doit être entre 1 et 10.",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
self.settings["spam_threshold"] = value
|
||||
save_settings(self.guild_id, self.settings)
|
||||
self.parent_view.settings = self.settings
|
||||
self.parent_view.build_view()
|
||||
|
||||
await interaction.response.send_message(f"✅ Le seuil a été réglé sur **{new_val}** messages.", ephemeral=True)
|
||||
await interaction.response.edit_message(view=self.parent_view)
|
||||
await interaction.followup.send(
|
||||
f"✅ Seuil anti-spam configuré sur **{value}** messages/5s",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
class RaidThresholdModal(discord.ui.Modal, title="⚔️ Seuil Anti-Raid"):
|
||||
"""Modal pour configurer le seuil anti-raid"""
|
||||
threshold_input = discord.ui.TextInput(
|
||||
label="Joins max en 10 secondes",
|
||||
placeholder="Entre 2 et 10 (défaut: 3)",
|
||||
min_length=1,
|
||||
max_length=2,
|
||||
required=True
|
||||
)
|
||||
|
||||
def __init__(self, guild_id: int, settings: dict, view: 'SecurityView'):
|
||||
super().__init__()
|
||||
self.guild_id = guild_id
|
||||
self.settings = settings
|
||||
self.parent_view = view
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
if not self.threshold_input.value.isdigit():
|
||||
return await interaction.response.send_message(
|
||||
"❌ Erreur : Veuillez entrer un nombre entier.",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
value = int(self.threshold_input.value)
|
||||
if value < 1 or value > 20:
|
||||
return await interaction.response.send_message(
|
||||
"❌ Erreur : La valeur doit être entre 1 et 20.",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
self.settings["raid_threshold"] = value
|
||||
save_settings(self.guild_id, self.settings)
|
||||
self.parent_view.settings = self.settings
|
||||
self.parent_view.build_view()
|
||||
|
||||
await interaction.response.edit_message(view=self.parent_view)
|
||||
await interaction.followup.send(
|
||||
f"✅ Seuil anti-raid configuré sur **{value}** joins/10s",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
class WhitelistModal(discord.ui.Modal, title="👤 Ajouter à la Whitelist"):
|
||||
"""Modal pour ajouter un utilisateur à la whitelist"""
|
||||
user_input = discord.ui.TextInput(
|
||||
label="ID de l'utilisateur",
|
||||
placeholder="Entrez l'ID Discord de l'utilisateur",
|
||||
min_length=15,
|
||||
max_length=20,
|
||||
required=True
|
||||
)
|
||||
|
||||
def __init__(self, guild_id: int, view: 'SecurityView'):
|
||||
super().__init__()
|
||||
self.guild_id = guild_id
|
||||
self.parent_view = view
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
if not self.user_input.value.isdigit():
|
||||
return await interaction.response.send_message(
|
||||
"❌ Erreur : ID invalide.",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
user_id = int(self.user_input.value)
|
||||
|
||||
# Charger et modifier la whitelist
|
||||
data = {}
|
||||
if os.path.exists(WHITELIST_FILE):
|
||||
try:
|
||||
with open(WHITELIST_FILE, "r", encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
guild_whitelist = data.get(str(self.guild_id), [])
|
||||
|
||||
if user_id in guild_whitelist:
|
||||
return await interaction.response.send_message(
|
||||
"❌ Cet utilisateur est déjà dans la whitelist.",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
guild_whitelist.append(user_id)
|
||||
data[str(self.guild_id)] = guild_whitelist
|
||||
|
||||
os.makedirs(os.path.dirname(WHITELIST_FILE), exist_ok=True)
|
||||
with open(WHITELIST_FILE, "w", encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
|
||||
self.parent_view.whitelist_count = len(guild_whitelist)
|
||||
self.parent_view.build_view()
|
||||
|
||||
await interaction.response.edit_message(view=self.parent_view)
|
||||
await interaction.followup.send(
|
||||
f"✅ Utilisateur ajouté à la whitelist.",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
# --- VUES DE L'INTERFACE ---
|
||||
|
||||
# --- VUE DE GESTION ---
|
||||
class SecurityView(discord.ui.View):
|
||||
def __init__(self, interaction, settings, category=None):
|
||||
super().__init__(timeout=None)
|
||||
"""Vue principale du panneau de sécurité"""
|
||||
|
||||
def __init__(self, interaction: discord.Interaction, settings: dict, category: Optional[str] = None):
|
||||
super().__init__(timeout=300) # 5 minutes timeout
|
||||
self.interaction = interaction
|
||||
self.guild = interaction.guild
|
||||
self.settings = settings
|
||||
self.category = category
|
||||
self.whitelist_count = len(load_whitelist(self.guild.id))
|
||||
self.build_view()
|
||||
|
||||
def build_view(self):
|
||||
"""Construit les boutons selon la catégorie"""
|
||||
self.clear_items()
|
||||
|
||||
if self.category is None:
|
||||
self.add_item(CategorySelect())
|
||||
# Menu principal
|
||||
self.add_item(MainCategorySelect())
|
||||
elif self.category == "spam":
|
||||
self.build_spam_view()
|
||||
elif self.category == "raid":
|
||||
self.build_raid_view()
|
||||
elif self.category == "nuke":
|
||||
self.build_nuke_view()
|
||||
elif self.category == "logs":
|
||||
self.build_logs_view()
|
||||
elif self.category == "whitelist":
|
||||
self.build_whitelist_view()
|
||||
|
||||
def build_spam_view(self):
|
||||
"""Construit la vue Anti-Spam"""
|
||||
# Toggle Anti-Spam
|
||||
anti_spam = self.settings.get("anti_spam", False)
|
||||
btn_antispam = ToggleButton(
|
||||
label="Anti-Spam",
|
||||
emoji="🛡️",
|
||||
is_enabled=anti_spam,
|
||||
custom_id="anti_spam"
|
||||
)
|
||||
self.add_item(btn_antispam)
|
||||
|
||||
# Toggle Anti-Lien
|
||||
anti_link = self.settings.get("anti_link", False)
|
||||
btn_antilink = ToggleButton(
|
||||
label="Anti-Liens",
|
||||
emoji="🔗",
|
||||
is_enabled=anti_link,
|
||||
custom_id="anti_link"
|
||||
)
|
||||
self.add_item(btn_antilink)
|
||||
|
||||
# Bouton de configuration du seuil
|
||||
threshold = self.settings.get("spam_threshold", 5)
|
||||
btn_threshold = discord.ui.Button(
|
||||
label=f"🔢 Seuil: {threshold} msg/5s",
|
||||
style=discord.ButtonStyle.blurple,
|
||||
custom_id="spam_threshold"
|
||||
)
|
||||
btn_threshold.callback = self.open_spam_threshold_modal
|
||||
self.add_item(btn_threshold)
|
||||
|
||||
# Bouton retour
|
||||
btn_back = discord.ui.Button(
|
||||
label="Retour",
|
||||
style=discord.ButtonStyle.gray,
|
||||
emoji="⬅️"
|
||||
)
|
||||
btn_back.callback = self.back_to_main
|
||||
self.add_item(btn_back)
|
||||
|
||||
def build_raid_view(self):
|
||||
"""Construit la vue Anti-Raid"""
|
||||
# Toggle Anti-Raid
|
||||
anti_raid = self.settings.get("anti_raid", False)
|
||||
btn_antiraid = ToggleButton(
|
||||
label="Anti-Raid",
|
||||
emoji="⚔️",
|
||||
is_enabled=anti_raid,
|
||||
custom_id="anti_raid"
|
||||
)
|
||||
self.add_item(btn_antiraid)
|
||||
|
||||
# Toggle Auto-Ban
|
||||
auto_ban = self.settings.get("auto_ban", False)
|
||||
btn_autoban = ToggleButton(
|
||||
label="Auto-Ban",
|
||||
emoji="🔨",
|
||||
is_enabled=auto_ban,
|
||||
custom_id="auto_ban"
|
||||
)
|
||||
self.add_item(btn_autoban)
|
||||
|
||||
# Bouton de configuration du seuil
|
||||
threshold = self.settings.get("raid_threshold", 3)
|
||||
btn_threshold = discord.ui.Button(
|
||||
label=f"🔢 Seuil: {threshold} join/10s",
|
||||
style=discord.ButtonStyle.blurple,
|
||||
custom_id="raid_threshold"
|
||||
)
|
||||
btn_threshold.callback = self.open_raid_threshold_modal
|
||||
self.add_item(btn_threshold)
|
||||
|
||||
# Bouton retour
|
||||
btn_back = discord.ui.Button(
|
||||
label="Retour",
|
||||
style=discord.ButtonStyle.gray,
|
||||
emoji="⬅️"
|
||||
)
|
||||
btn_back.callback = self.back_to_main
|
||||
self.add_item(btn_back)
|
||||
|
||||
def build_nuke_view(self):
|
||||
"""Construit la vue Anti-Nuke"""
|
||||
# Toggle Anti-Salon
|
||||
anti_channel = self.settings.get("anti_channel_delete", False)
|
||||
btn_channel = ToggleButton(
|
||||
label="Anti-Salon",
|
||||
emoji="📁",
|
||||
is_enabled=anti_channel,
|
||||
custom_id="anti_channel_delete"
|
||||
)
|
||||
self.add_item(btn_channel)
|
||||
|
||||
# Toggle Anti-Rôle
|
||||
anti_role = self.settings.get("anti_role_create", False)
|
||||
btn_role = ToggleButton(
|
||||
label="Anti-Rôle",
|
||||
emoji="🎭",
|
||||
is_enabled=anti_role,
|
||||
custom_id="anti_role_create"
|
||||
)
|
||||
self.add_item(btn_role)
|
||||
|
||||
# Bouton retour
|
||||
btn_back = discord.ui.Button(
|
||||
label="Retour",
|
||||
style=discord.ButtonStyle.gray,
|
||||
emoji="⬅️"
|
||||
)
|
||||
btn_back.callback = self.back_to_main
|
||||
self.add_item(btn_back)
|
||||
|
||||
def build_logs_view(self):
|
||||
"""Construit la vue Logs"""
|
||||
# Toggle Logs
|
||||
logs_enabled = self.settings.get("server_logs", False)
|
||||
btn_logs = ToggleButton(
|
||||
label="Logs Serveur",
|
||||
emoji="📋",
|
||||
is_enabled=logs_enabled,
|
||||
custom_id="server_logs"
|
||||
)
|
||||
self.add_item(btn_logs)
|
||||
|
||||
# ID du channel de logs (affichage)
|
||||
log_channel_id = self.settings.get("log_channel_id")
|
||||
if log_channel_id:
|
||||
channel = self.guild.get_channel(int(log_channel_id))
|
||||
channel_mention = channel.mention if channel else f"`{log_channel_id}`"
|
||||
btn_channel = discord.ui.Button(
|
||||
label=f"📌 Channel: {channel.name if channel else 'Invalide'}",
|
||||
style=discord.ButtonStyle.green,
|
||||
disabled=True
|
||||
)
|
||||
else:
|
||||
config_map = {
|
||||
"raid": [("anti_raid", "🛡️ Anti-Raid"), ("auto_ban", "🔨 Auto-Ban")],
|
||||
"spam": [("anti_spam", "🛡️ Anti-Spam"), ("anti_link", "🔗 Anti-Link"), ("anti_mention", "👥 Anti-Mention")],
|
||||
"nuke": [("anti_channel_delete", "📁 Anti-Salon"), ("anti_role_create", "🎭 Anti-Rôle")],
|
||||
"logs": [("server_logs", "📋 Activer Logs")]
|
||||
}
|
||||
btn_channel = discord.ui.Button(
|
||||
label="📌 Aucun channel configuré",
|
||||
style=discord.ButtonStyle.red,
|
||||
disabled=True
|
||||
)
|
||||
self.add_item(btn_channel)
|
||||
|
||||
for key, label in config_map.get(self.category, []):
|
||||
is_on = self.settings.get(key, False)
|
||||
style = discord.ButtonStyle.green if is_on else discord.ButtonStyle.red
|
||||
status = "✅" if is_on else "❌"
|
||||
btn = discord.ui.Button(label=f"{label} [{status}]", style=style, custom_id=key)
|
||||
btn.callback = self.toggle_setting
|
||||
self.add_item(btn)
|
||||
# Bouton retour
|
||||
btn_back = discord.ui.Button(
|
||||
label="Retour",
|
||||
style=discord.ButtonStyle.gray,
|
||||
emoji="⬅️"
|
||||
)
|
||||
btn_back.callback = self.back_to_main
|
||||
self.add_item(btn_back)
|
||||
|
||||
# BOUTON SPÉCIAL : RÉGLAGE SEUIL (uniquement dans spam)
|
||||
if self.category == "spam":
|
||||
btn_threshold = discord.ui.Button(label=f"🔢 Seuil: {self.settings.get('spam_threshold', 5)}", style=discord.ButtonStyle.blurple)
|
||||
btn_threshold.callback = self.open_threshold_modal
|
||||
self.add_item(btn_threshold)
|
||||
def build_whitelist_view(self):
|
||||
"""Construit la vue Whitelist"""
|
||||
# Affichage du nombre avec style
|
||||
btn_count = discord.ui.Button(
|
||||
label=f"👥 Whitelist: {self.whitelist_count} utilisateur(s)",
|
||||
style=discord.ButtonStyle.blurple,
|
||||
disabled=True
|
||||
)
|
||||
self.add_item(btn_count)
|
||||
|
||||
back_btn = discord.ui.Button(label="⬅️ Retour", style=discord.ButtonStyle.gray)
|
||||
back_btn.callback = self.back_to_main
|
||||
self.add_item(back_btn)
|
||||
# Menu dropdown pour gérer la whitelist
|
||||
if self.whitelist_count > 0:
|
||||
self.add_item(WhitelistSelect(self.guild, self))
|
||||
else:
|
||||
# Message si whitelist vide
|
||||
btn_empty = discord.ui.Button(
|
||||
label="📭 Aucun utilisateur dans la whitelist",
|
||||
style=discord.ButtonStyle.gray,
|
||||
disabled=True
|
||||
)
|
||||
self.add_item(btn_empty)
|
||||
|
||||
async def toggle_setting(self, interaction: discord.Interaction):
|
||||
key = interaction.data['custom_id']
|
||||
# Bouton ajouter
|
||||
btn_add = discord.ui.Button(
|
||||
label="➕ Ajouter",
|
||||
style=discord.ButtonStyle.green,
|
||||
emoji="➕",
|
||||
custom_id="whitelist_add"
|
||||
)
|
||||
btn_add.callback = self.open_whitelist_modal
|
||||
self.add_item(btn_add)
|
||||
|
||||
# Bouton retour
|
||||
btn_back = discord.ui.Button(
|
||||
label="Retour",
|
||||
style=discord.ButtonStyle.gray,
|
||||
emoji="⬅️"
|
||||
)
|
||||
btn_back.callback = self.back_to_main
|
||||
self.add_item(btn_back)
|
||||
|
||||
async def toggle_setting(self, interaction: discord.Interaction, key: str):
|
||||
"""Bascule un paramètre"""
|
||||
self.settings[key] = not self.settings.get(key, False)
|
||||
save_settings(self.guild.id, self.settings)
|
||||
self.build_view()
|
||||
await interaction.response.edit_message(view=self)
|
||||
|
||||
async def open_threshold_modal(self, interaction: discord.Interaction):
|
||||
await interaction.response.send_modal(ThresholdModal(self.guild.id, self.settings, self))
|
||||
async def open_spam_threshold_modal(self, interaction: discord.Interaction):
|
||||
"""Ouvre le modal de configuration du seuil spam"""
|
||||
await interaction.response.send_modal(
|
||||
SpamThresholdModal(self.guild.id, self.settings, self)
|
||||
)
|
||||
|
||||
async def open_raid_threshold_modal(self, interaction: discord.Interaction):
|
||||
"""Ouvre le modal de configuration du seuil raid"""
|
||||
await interaction.response.send_modal(
|
||||
RaidThresholdModal(self.guild.id, self.settings, self)
|
||||
)
|
||||
|
||||
async def open_whitelist_modal(self, interaction: discord.Interaction):
|
||||
"""Ouvre le modal d'ajout à la whitelist"""
|
||||
await interaction.response.send_modal(
|
||||
WhitelistModal(self.guild.id, self)
|
||||
)
|
||||
|
||||
async def back_to_main(self, interaction: discord.Interaction):
|
||||
"""Retour au menu principal"""
|
||||
self.category = None
|
||||
self.build_view()
|
||||
embed = self.create_main_embed()
|
||||
embed = self.create_main_embed(interaction.user)
|
||||
await interaction.response.edit_message(embed=embed, view=self)
|
||||
|
||||
def create_main_embed(self):
|
||||
embed = discord.Embed(title="🛡️ Centre de Sécurité Kuby", description="Interface modulaire de protection.", color=0x2b2d31)
|
||||
if self.guild.icon: embed.set_thumbnail(url=self.guild.icon.url)
|
||||
embed.add_field(name="Propriétaire", value=self.guild.owner.mention)
|
||||
def create_main_embed(self, user: discord.Member = None) -> discord.Embed:
|
||||
"""Crée l'embed du menu principal"""
|
||||
embed = discord.Embed(
|
||||
title="🛡️ Centre de Sécurité Kuby",
|
||||
description="Gérez la protection de votre serveur",
|
||||
color=0x2b2d31
|
||||
)
|
||||
|
||||
if self.guild.icon:
|
||||
embed.set_thumbnail(url=self.guild.icon.url)
|
||||
|
||||
# Informations du serveur
|
||||
embed.add_field(
|
||||
name="🏠 Serveur",
|
||||
value=self.guild.name,
|
||||
inline=True
|
||||
)
|
||||
|
||||
embed.add_field(
|
||||
name="👤 Propriétaire",
|
||||
value=self.guild.owner.mention if self.guild.owner else "Inconnu",
|
||||
inline=True
|
||||
)
|
||||
|
||||
# Statut global
|
||||
active_count = sum([
|
||||
self.settings.get("anti_spam", False),
|
||||
self.settings.get("anti_link", False),
|
||||
self.settings.get("anti_raid", False),
|
||||
self.settings.get("auto_ban", False),
|
||||
self.settings.get("anti_channel_delete", False),
|
||||
self.settings.get("anti_role_create", False),
|
||||
self.settings.get("server_logs", False)
|
||||
])
|
||||
total_options = 7
|
||||
status = "🟢 Actif" if active_count > 0 else "🔴 Inactif"
|
||||
|
||||
embed.add_field(
|
||||
name="⚡ Protection",
|
||||
value=f"{status} ({active_count}/{total_options})",
|
||||
inline=True
|
||||
)
|
||||
|
||||
# Avertissement si pas admin
|
||||
if user and not is_immune(self.guild, user):
|
||||
embed.set_footer(text="⚠️ Vous n'êtes pas administrateur. Accès restreint.")
|
||||
|
||||
return embed
|
||||
|
||||
class CategorySelect(discord.ui.Select):
|
||||
def create_category_embed(self, title: str, description: str) -> discord.Embed:
|
||||
"""Crée un embed pour une catégorie"""
|
||||
embed = discord.Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
color=0x2b2d31
|
||||
)
|
||||
|
||||
if self.guild.icon:
|
||||
embed.set_thumbnail(url=self.guild.icon.url)
|
||||
|
||||
return embed
|
||||
|
||||
|
||||
class ToggleButton(discord.ui.Button):
|
||||
"""Bouton de bascule avec indicateur de statut"""
|
||||
|
||||
def __init__(self, label: str, emoji: str, is_enabled: bool, custom_id: str):
|
||||
style = discord.ButtonStyle.green if is_enabled else discord.ButtonStyle.red
|
||||
status = "✅" if is_enabled else "❌"
|
||||
super().__init__(
|
||||
label=f"{label} [{status}]",
|
||||
style=style,
|
||||
custom_id=custom_id,
|
||||
emoji=emoji
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
view: SecurityView = self.view
|
||||
key = interaction.data['custom_id']
|
||||
|
||||
# Vérifier les permissions
|
||||
if not is_immune(view.guild, interaction.user):
|
||||
return await interaction.response.send_message(
|
||||
"❌ Permissions insuffisantes.",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
# Basculer le paramètre
|
||||
view.settings[key] = not view.settings.get(key, False)
|
||||
save_settings(view.guild.id, view.settings)
|
||||
view.build_view()
|
||||
await interaction.response.edit_message(view=view)
|
||||
|
||||
|
||||
class MainCategorySelect(discord.ui.Select):
|
||||
"""Menu de sélection des catégories"""
|
||||
|
||||
def __init__(self):
|
||||
options = [
|
||||
discord.SelectOption(label="Anti-Raid", value="raid", emoji="⚔️"),
|
||||
discord.SelectOption(label="Anti-Spam", value="spam", emoji="🛡️"),
|
||||
discord.SelectOption(label="Anti-Nuke", value="nuke", emoji="☢️"),
|
||||
discord.SelectOption(label="Logs", value="logs", emoji="📋")
|
||||
discord.SelectOption(
|
||||
label="Anti-Spam",
|
||||
emoji="🛡️",
|
||||
value="spam",
|
||||
description="Protection contre le spam et les liens"
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="Anti-Raid",
|
||||
emoji="⚔️",
|
||||
value="raid",
|
||||
description="Protection contre les raids"
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="Anti-Nuke",
|
||||
emoji="☢️",
|
||||
value="nuke",
|
||||
description="Protection contre la destruction du serveur"
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="Logs",
|
||||
emoji="📋",
|
||||
value="logs",
|
||||
description="Journalisation des événements"
|
||||
),
|
||||
discord.SelectOption(
|
||||
label="Whitelist",
|
||||
emoji="👥",
|
||||
value="whitelist",
|
||||
description="Gérer les utilisateurs immunisés"
|
||||
)
|
||||
]
|
||||
super().__init__(placeholder="Sélectionnez un module...", options=options)
|
||||
super().__init__(
|
||||
placeholder="Sélectionnez une catégorie...",
|
||||
options=options,
|
||||
min_values=1,
|
||||
max_values=1
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
view: SecurityView = self.view
|
||||
view.category = self.values[0]
|
||||
view.build_view()
|
||||
embed = discord.Embed(title=f"⚙️ Configuration : {view.category.upper()}", color=0x2b2d31)
|
||||
|
||||
# Créer l'embed de la catégorie
|
||||
category_embeds = {
|
||||
"spam": view.create_category_embed(
|
||||
"Configuration Anti-Spam",
|
||||
"Paramètres de protection contre le spam et les liens.\n\n"
|
||||
"• **Anti-Spam** : Détecte les messages répétitifs\n"
|
||||
"• **Anti-Liens** : Bloque les liens Discord et URLs"
|
||||
),
|
||||
"raid": view.create_category_embed(
|
||||
"Configuration Anti-Raid",
|
||||
"Paramètres de protection contre les raids.\n\n"
|
||||
"• **Anti-Raid** : Détecte les vagues de join\n"
|
||||
"• **Auto-Ban** : Banne automatiquement les raiders"
|
||||
),
|
||||
"nuke": view.create_category_embed(
|
||||
"Configuration Anti-Nuke",
|
||||
"Paramètres de protection contre la destruction.\n\n"
|
||||
"• **Anti-Salon** : Protège les salons\n"
|
||||
"• **Anti-Rôle** : Protège les rôles"
|
||||
),
|
||||
"logs": view.create_category_embed(
|
||||
"Configuration Logs",
|
||||
"Paramètres de journalisation des événements.\n\n"
|
||||
"• **Logs Serveur** : Active la journalisation\n"
|
||||
"• **Channel** : Salon où seront envoyés les logs"
|
||||
),
|
||||
"whitelist": view.create_category_embed(
|
||||
"Gestion Whitelist",
|
||||
"Gérez les utilisateurs immunisés aux protections.\n\n"
|
||||
"Ces utilisateurs ne seront jamais sanctionnés."
|
||||
)
|
||||
}
|
||||
|
||||
embed = category_embeds.get(view.category, view.create_main_embed())
|
||||
await interaction.response.edit_message(embed=embed, view=view)
|
||||
|
||||
# --- COG ---
|
||||
|
||||
class WhitelistSelect(discord.ui.Select):
|
||||
"""Menu dropdown pour voir et gérer les utilisateurs whitelistés"""
|
||||
|
||||
def __init__(self, guild: discord.Guild, view: 'SecurityView'):
|
||||
self.guild = guild
|
||||
self.parent_view = view
|
||||
whitelist = load_whitelist(guild.id)
|
||||
|
||||
options = []
|
||||
for user_id in whitelist:
|
||||
# Essayer de trouver l'utilisateur
|
||||
member = guild.get_member(user_id)
|
||||
if member:
|
||||
label = f"{member.name} ({member.id})"
|
||||
emoji = "👤"
|
||||
else:
|
||||
label = f"ID: {user_id} (non trouvé)"
|
||||
emoji = "❓"
|
||||
options.append(
|
||||
discord.SelectOption(
|
||||
label=label[:100], # Discord limite à 100 chars
|
||||
value=str(user_id),
|
||||
emoji=emoji,
|
||||
description="Cliquez pour retirer de la whitelist"
|
||||
)
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
placeholder="👥 Gérer la whitelist (cliquez pour retirer)...",
|
||||
options=options,
|
||||
min_values=1,
|
||||
max_values=1
|
||||
)
|
||||
|
||||
async def callback(self, interaction: discord.Interaction):
|
||||
"""Gère la sélection d'un utilisateur à retirer"""
|
||||
user_id = int(self.values[0])
|
||||
|
||||
# Charger et modifier la whitelist
|
||||
data = {}
|
||||
if os.path.exists(WHITELIST_FILE):
|
||||
try:
|
||||
with open(WHITELIST_FILE, "r", encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
pass
|
||||
|
||||
guild_whitelist = data.get(str(self.guild.id), [])
|
||||
|
||||
if user_id not in guild_whitelist:
|
||||
return await interaction.response.send_message(
|
||||
"❌ Utilisateur non trouvé dans la whitelist.",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
# Retirer l'utilisateur
|
||||
guild_whitelist.remove(user_id)
|
||||
data[str(self.guild.id)] = guild_whitelist
|
||||
|
||||
os.makedirs(os.path.dirname(WHITELIST_FILE), exist_ok=True)
|
||||
with open(WHITELIST_FILE, "w", encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||||
|
||||
# Mettre à jour l'affichage
|
||||
self.parent_view.whitelist_count = len(guild_whitelist)
|
||||
self.parent_view.build_view()
|
||||
|
||||
await interaction.response.edit_message(view=self.parent_view)
|
||||
await interaction.followup.send(
|
||||
"✅ Utilisateur retiré de la whitelist.",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
|
||||
class Security(commands.Cog):
|
||||
"""Cog principal de gestion de la sécurité"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
@app_commands.command(name="security", description="Ouvrir le panneau Kuby")
|
||||
@app_commands.command(
|
||||
name="security",
|
||||
description="🛡️ Ouvrir le panneau de sécurité Kuby"
|
||||
)
|
||||
async def security(self, interaction: discord.Interaction):
|
||||
if not (interaction.user.guild_permissions.administrator or
|
||||
interaction.user.id == interaction.guild.owner_id or
|
||||
is_whitelisted(interaction.guild.id, interaction.user.id)):
|
||||
return await interaction.response.send_message("❌ Accès refusé.", ephemeral=True)
|
||||
"""
|
||||
Commande principale pour ouvrir l'interface de sécurité.
|
||||
Accessible uniquement aux administrateurs et propriétaires whitelistés.
|
||||
"""
|
||||
# Vérification des permissions
|
||||
if not is_immune(interaction.guild, interaction.user):
|
||||
if interaction.response.is_done():
|
||||
return await interaction.followup.send(
|
||||
"❌ **Accès refusé**\n\nVous devez être administrateur, propriétaire du serveur, ou être dans la whitelist pour accéder à ce panneau.",
|
||||
ephemeral=True
|
||||
)
|
||||
return await interaction.response.send_message(
|
||||
"❌ **Accès refusé**\n\nVous devez être administrateur, propriétaire du serveur, ou être dans la whitelist pour accéder à ce panneau.",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
# Chargement des paramètres
|
||||
settings = load_settings(interaction.guild.id)
|
||||
|
||||
# Création de la vue
|
||||
view = SecurityView(interaction, settings)
|
||||
await interaction.response.send_message(embed=view.create_main_embed(), view=view)
|
||||
embed = view.create_main_embed(interaction.user)
|
||||
|
||||
if interaction.response.is_done():
|
||||
await interaction.followup.send(
|
||||
embed=embed,
|
||||
view=view,
|
||||
ephemeral=True
|
||||
)
|
||||
else:
|
||||
await interaction.response.send_message(
|
||||
embed=embed,
|
||||
view=view,
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
@security.error
|
||||
async def security_error(self, interaction: discord.Interaction, error: app_commands.AppCommandError):
|
||||
"""Gestion des erreurs de la commande security"""
|
||||
# Ne pas envoyer de message si l'interaction a déjà été traitée
|
||||
if interaction.response.is_done():
|
||||
return
|
||||
|
||||
if isinstance(error, app_commands.MissingPermissions):
|
||||
await interaction.response.send_message(
|
||||
"❌ Permissions insuffisantes.",
|
||||
ephemeral=True
|
||||
)
|
||||
else:
|
||||
await interaction.response.send_message(
|
||||
f"❌ Une erreur est survenue.",
|
||||
ephemeral=True
|
||||
)
|
||||
|
||||
|
||||
async def setup(bot):
|
||||
"""Setup du cog de sécurité"""
|
||||
await bot.add_cog(Security(bot))
|
||||
|
||||
|
|
|
|||
59
data/logs/messages_2026-01-02.json
Normal file
59
data/logs/messages_2026-01-02.json
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
[
|
||||
{
|
||||
"timestamp": "2026-01-02T13:30:27.725564",
|
||||
"event_type": "deleted",
|
||||
"message_id": 1456625676537823243,
|
||||
"channel_id": 1369669999677145098,
|
||||
"channel_name": "╭💬・général",
|
||||
"author_id": 971446412690722826,
|
||||
"author_name": "gameurpro12",
|
||||
"author_roles": [
|
||||
1369669999345537145,
|
||||
1369669999366770723,
|
||||
1403041729330024508,
|
||||
1369669999366770726
|
||||
],
|
||||
"content": "gg",
|
||||
"attachments": [],
|
||||
"embeds": [],
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-02T13:30:27.754495",
|
||||
"event_type": "deleted",
|
||||
"message_id": 1456625676537823243,
|
||||
"channel_id": 1369669999677145098,
|
||||
"channel_name": "╭💬・général",
|
||||
"author_id": 971446412690722826,
|
||||
"author_name": "gameurpro12",
|
||||
"author_roles": [
|
||||
1369669999345537145,
|
||||
1369669999366770723,
|
||||
1403041729330024508,
|
||||
1369669999366770726
|
||||
],
|
||||
"content": "gg",
|
||||
"attachments": [],
|
||||
"embeds": [],
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2026-01-02T13:30:37.215876",
|
||||
"event_type": "deleted",
|
||||
"message_id": 1456625680153444448,
|
||||
"channel_id": 1369669999677145098,
|
||||
"channel_name": "╭💬・général",
|
||||
"author_id": 971446412690722826,
|
||||
"author_name": "gameurpro12",
|
||||
"author_roles": [
|
||||
1369669999345537145,
|
||||
1369669999366770723,
|
||||
1403041729330024508,
|
||||
1369669999366770726
|
||||
],
|
||||
"content": "gg",
|
||||
"attachments": [],
|
||||
"embeds": [],
|
||||
"details": {}
|
||||
}
|
||||
]
|
||||
Loading…
Add table
Add a link
Reference in a new issue