506 lines
19 KiB
Python
506 lines
19 KiB
Python
import disnake
|
|
from disnake.ext import commands
|
|
import json
|
|
import os
|
|
from typing import Optional
|
|
|
|
# Chemins ABSOLUS
|
|
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")
|
|
|
|
# Créer le dossier data/ s'il n'existe pas
|
|
os.makedirs(DATA_DIR, exist_ok=True)
|
|
|
|
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(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)
|
|
|
|
|
|
# --- MODALS DE CONFIGURATION DU RÈGLEMENT (BACK-END) ---
|
|
|
|
class RulesTitleModal(disnake.ui.Modal):
|
|
"""Modal pour configurer le titre du règlement"""
|
|
title_input = disnake.ui.TextInput(
|
|
label="Titre du règlement",
|
|
placeholder="Ex: 📜 Règlement de [Nom du Serveur]",
|
|
min_length=1,
|
|
max_length=100,
|
|
required=True
|
|
)
|
|
|
|
def __init__(self, guild_id: int, settings: dict, on_update_callback=None):
|
|
super().__init__(title="📜 Titre du Règlement", components=[self.title_input])
|
|
self.guild_id = guild_id
|
|
self.settings = settings
|
|
self.on_update_callback = on_update_callback
|
|
|
|
async def callback(self, interaction: disnake.Interaction):
|
|
self.settings["rules_title"] = self.title_input.value
|
|
save_settings(self.guild_id, self.settings)
|
|
|
|
if self.on_update_callback:
|
|
await self.on_update_callback(interaction, self.settings)
|
|
else:
|
|
await interaction.response.edit_message(view=self._create_view(interaction))
|
|
|
|
await interaction.followup.send(
|
|
f"✅ Titre du règlement mis à jour.",
|
|
ephemeral=True
|
|
)
|
|
|
|
def _create_view(self, interaction):
|
|
"""Crée la vue mise à jour"""
|
|
from commandes.security import SecurityView
|
|
# Essayer d'obtenir le bot depuis l'interaction
|
|
bot = getattr(interaction, 'client', None)
|
|
view = SecurityView(interaction, self.settings, category="rules", bot=bot)
|
|
return view
|
|
|
|
|
|
class RulesContentModal(disnake.ui.Modal):
|
|
"""Modal pour configurer le contenu du règlement"""
|
|
content_input = disnake.ui.TextInput(
|
|
label="Contenu du règlement",
|
|
placeholder="Entrez les règles de votre serveur...",
|
|
style=disnake.TextInputStyle.paragraph,
|
|
min_length=10,
|
|
max_length=4000,
|
|
required=True
|
|
)
|
|
|
|
def __init__(self, guild_id: int, settings: dict, on_update_callback=None):
|
|
super().__init__(title="📝 Contenu du Règlement", components=[self.content_input])
|
|
self.guild_id = guild_id
|
|
self.settings = settings
|
|
self.on_update_callback = on_update_callback
|
|
|
|
async def callback(self, interaction: disnake.Interaction):
|
|
self.settings["rules_content"] = self.content_input.value
|
|
save_settings(self.guild_id, self.settings)
|
|
|
|
if self.on_update_callback:
|
|
await self.on_update_callback(interaction, self.settings)
|
|
else:
|
|
await interaction.response.edit_message(view=self._create_view(interaction))
|
|
|
|
await interaction.followup.send(
|
|
f"✅ Contenu du règlement mis à jour.",
|
|
ephemeral=True
|
|
)
|
|
|
|
def _create_view(self, interaction):
|
|
"""Crée la vue mise à jour"""
|
|
from commandes.security import SecurityView
|
|
# Essayer d'obtenir le bot depuis l'interaction
|
|
bot = getattr(interaction, 'client', None)
|
|
view = SecurityView(interaction, self.settings, category="rules", bot=bot)
|
|
return view
|
|
|
|
|
|
class RulesChannelModal(disnake.ui.Modal):
|
|
"""Modal pour sélectionner le salon du règlement"""
|
|
channel_input = disnake.ui.TextInput(
|
|
label="ID du salon",
|
|
placeholder="Entrez l'ID du salon où envoyer le règlement",
|
|
min_length=15,
|
|
max_length=20,
|
|
required=True
|
|
)
|
|
|
|
def __init__(self, guild_id: int, settings: dict, on_update_callback=None):
|
|
super().__init__(title="📌 Salon du Règlement", components=[self.channel_input])
|
|
self.guild_id = guild_id
|
|
self.settings = settings
|
|
self.on_update_callback = on_update_callback
|
|
|
|
async def callback(self, interaction: disnake.Interaction):
|
|
if not self.channel_input.value.isdigit():
|
|
return await interaction.response.send_message(
|
|
"❌ Erreur : ID invalide.",
|
|
ephemeral=True
|
|
)
|
|
|
|
channel_id = int(self.channel_input.value)
|
|
channel = interaction.guild.get_channel(channel_id)
|
|
|
|
if not channel:
|
|
return await interaction.response.send_message(
|
|
"❌ Salon non trouvé. Vérifiez l'ID.",
|
|
ephemeral=True
|
|
)
|
|
|
|
self.settings["rules_channel_id"] = channel_id
|
|
save_settings(self.guild_id, self.settings)
|
|
|
|
if self.on_update_callback:
|
|
await self.on_update_callback(interaction, self.settings)
|
|
else:
|
|
await interaction.response.edit_message(view=self._create_view(interaction))
|
|
|
|
await interaction.followup.send(
|
|
f"✅ Salon du règlement défini sur {channel.mention}.",
|
|
ephemeral=True
|
|
)
|
|
|
|
def _create_view(self, interaction):
|
|
"""Crée la vue mise à jour"""
|
|
from commandes.security import SecurityView
|
|
# Essayer d'obtenir le bot depuis l'interaction
|
|
bot = getattr(interaction, 'client', None)
|
|
view = SecurityView(interaction, self.settings, category="rules", bot=bot)
|
|
return view
|
|
|
|
|
|
class RulesRoleModal(disnake.ui.Modal):
|
|
"""Modal pour configurer le rôle attribué à l'acceptation"""
|
|
role_input = disnake.ui.TextInput(
|
|
label="ID du rôle",
|
|
placeholder="Entrez l'ID du rôle à attribuer",
|
|
min_length=15,
|
|
max_length=20,
|
|
required=True
|
|
)
|
|
|
|
def __init__(self, guild_id: int, settings: dict, on_update_callback=None):
|
|
super().__init__(title="✅ Rôle d'Acceptation", components=[self.role_input])
|
|
self.guild_id = guild_id
|
|
self.settings = settings
|
|
self.on_update_callback = on_update_callback
|
|
|
|
async def callback(self, interaction: disnake.Interaction):
|
|
if not self.role_input.value.isdigit():
|
|
return await interaction.response.send_message(
|
|
"❌ Erreur : ID invalide.",
|
|
ephemeral=True
|
|
)
|
|
|
|
role_id = int(self.role_input.value)
|
|
role = interaction.guild.get_role(role_id)
|
|
|
|
if not role:
|
|
return await interaction.response.send_message(
|
|
"❌ Rôle non trouvé. Vérifiez l'ID.",
|
|
ephemeral=True
|
|
)
|
|
|
|
self.settings["rules_accept_role_id"] = role_id
|
|
save_settings(self.guild_id, self.settings)
|
|
|
|
if self.on_update_callback:
|
|
await self.on_update_callback(interaction, self.settings)
|
|
else:
|
|
await interaction.response.edit_message(view=self._create_view(interaction))
|
|
|
|
await interaction.followup.send(
|
|
f"✅ Rôle d'acceptation défini sur **{role.name}**.",
|
|
ephemeral=True
|
|
)
|
|
|
|
def _create_view(self, interaction):
|
|
"""Crée la vue mise à jour"""
|
|
from commandes.security import SecurityView
|
|
# Essayer d'obtenir le bot depuis l'interaction
|
|
bot = getattr(interaction, 'client', None)
|
|
view = SecurityView(interaction, self.settings, category="rules", bot=bot)
|
|
return view
|
|
|
|
|
|
class RulesMessageTypeModal(disnake.ui.Modal):
|
|
"""Modal pour choisir le type de message du règlement"""
|
|
message_type = disnake.ui.TextInput(
|
|
label="Type de message",
|
|
placeholder="Tapez 'embed' pour embed ou 'simple' pour message simple",
|
|
min_length=5,
|
|
max_length=6,
|
|
required=True
|
|
)
|
|
|
|
def __init__(self, guild_id: int, settings: dict, on_update_callback=None):
|
|
super().__init__(title="📝 Type de Message", components=[self.message_type])
|
|
self.guild_id = guild_id
|
|
self.settings = settings
|
|
self.on_update_callback = on_update_callback
|
|
|
|
async def callback(self, interaction: disnake.Interaction):
|
|
msg_type = self.message_type.value.lower().strip()
|
|
|
|
if msg_type not in ["embed", "simple"]:
|
|
return await interaction.response.send_message(
|
|
"❌ Erreur : Tapez 'embed' ou 'simple'.",
|
|
ephemeral=True
|
|
)
|
|
|
|
self.settings["rules_message_type"] = msg_type
|
|
save_settings(self.guild_id, self.settings)
|
|
|
|
if self.on_update_callback:
|
|
await self.on_update_callback(interaction, self.settings)
|
|
else:
|
|
await interaction.response.edit_message(view=self._create_view(interaction))
|
|
|
|
type_name = "Embed" if msg_type == "embed" else "Message simple"
|
|
await interaction.followup.send(
|
|
f"✅ Type de message défini sur **{type_name}**.",
|
|
ephemeral=True
|
|
)
|
|
|
|
def _create_view(self, interaction):
|
|
"""Crée la vue mise à jour"""
|
|
from commandes.security import SecurityView
|
|
# Essayer d'obtenir le bot depuis l'interaction
|
|
bot = getattr(interaction, 'client', None)
|
|
view = SecurityView(interaction, self.settings, category="rules", bot=bot)
|
|
return view
|
|
|
|
|
|
class ToggleRulesButton(disnake.ui.Button):
|
|
"""Bouton de bascule pour activer/désactiver le règlement"""
|
|
|
|
def __init__(self, is_enabled: bool):
|
|
style = disnake.ButtonStyle.green if is_enabled else disnake.ButtonStyle.red
|
|
status = "✅" if is_enabled else "❌"
|
|
super().__init__(
|
|
label=f"Règlement [{status}]",
|
|
style=style,
|
|
custom_id="rules_enabled",
|
|
emoji="📜"
|
|
)
|
|
|
|
async def callback(self, interaction: disnake.Interaction):
|
|
view = self.view
|
|
settings = view.settings
|
|
guild = interaction.guild
|
|
|
|
# Basculer le paramètre
|
|
settings["rules_enabled"] = not settings.get("rules_enabled", False)
|
|
save_settings(guild.id, settings)
|
|
|
|
# Reconstruire la vue
|
|
from commandes.security import SecurityView
|
|
# Essayer d'obtenir le bot depuis l'interaction
|
|
bot = getattr(interaction, 'client', None)
|
|
new_view = SecurityView(interaction, settings, category="rules", bot=bot)
|
|
|
|
# Recréer l'embed
|
|
embed = new_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."
|
|
)
|
|
|
|
await interaction.response.edit_message(embed=embed, view=new_view)
|
|
|
|
|
|
class RulesAcceptButton(disnake.ui.View):
|
|
"""Bouton persistant pour accepter le règlement"""
|
|
|
|
def __init__(self, guild_id: int, accept_role_id: int):
|
|
super().__init__(timeout=None) # Pas de timeout - bouton persistant
|
|
self.guild_id = guild_id
|
|
self.accept_role_id = accept_role_id
|
|
|
|
@disnake.ui.button(
|
|
label="J'accepte le règlement",
|
|
emoji="✅",
|
|
style=disnake.ButtonStyle.green,
|
|
custom_id="rules_accept_button"
|
|
)
|
|
async def accept_rules(self, button: disnake.ui.Button, interaction: disnake.Interaction):
|
|
"""Gère le clic sur le bouton d'acceptation"""
|
|
guild = interaction.guild
|
|
|
|
# Vérifier si le bouton est toujours activé (activé dans les paramètres)
|
|
settings = load_settings(guild.id)
|
|
if not settings.get("rules_enabled", False):
|
|
return await interaction.response.send_message(
|
|
"❌ Cette fonctionnalité a été désactivée.",
|
|
ephemeral=True
|
|
)
|
|
|
|
# Vérifier si l'utilisateur a déjà le rôle
|
|
role = guild.get_role(self.accept_role_id)
|
|
if not role:
|
|
return await interaction.response.send_message(
|
|
"❌ Le rôle d'acceptation n'est plus configuré sur ce serveur.",
|
|
ephemeral=True
|
|
)
|
|
|
|
member = interaction.user
|
|
|
|
if role in member.roles:
|
|
return await interaction.response.send_message(
|
|
"✅ Vous avez déjà accepté le règlement !",
|
|
ephemeral=True
|
|
)
|
|
|
|
# Attribuer le rôle à l'utilisateur
|
|
try:
|
|
await member.add_roles(role)
|
|
|
|
# Envoyer une confirmation éphémère
|
|
await interaction.response.send_message(
|
|
f"✅ {member.mention}, vous avez accepté le règlement !\n"
|
|
f"Le rôle **{role.name}** vous a été attribué.",
|
|
ephemeral=True
|
|
)
|
|
except disnake.Forbidden:
|
|
await interaction.response.send_message(
|
|
"❌ Je n'ai pas les permissions pour attribuer ce rôle.",
|
|
ephemeral=True
|
|
)
|
|
except Exception as e:
|
|
await interaction.response.send_message(
|
|
f"❌ Une erreur est survenue: {e}",
|
|
ephemeral=True
|
|
)
|
|
|
|
|
|
class RulesCog(commands.Cog):
|
|
"""Cog pour la gestion du règlement"""
|
|
|
|
def __init__(self, bot):
|
|
self.bot = bot
|
|
self.persistent_views_added = False
|
|
|
|
async def cog_load(self):
|
|
"""Called when cog is loaded - setup persistent views"""
|
|
await self.setup_persistent_views()
|
|
|
|
async def setup_persistent_views(self):
|
|
"""Configure les views persistantes pour les règlements existants"""
|
|
if self.persistent_views_added:
|
|
return
|
|
|
|
# Charger les paramètres de tous les serveurs
|
|
if not os.path.exists(SETTINGS_FILE):
|
|
return
|
|
|
|
try:
|
|
with open(SETTINGS_FILE, "r", encoding='utf-8') as f:
|
|
all_settings = json.load(f)
|
|
|
|
# Pour chaque serveur avec le règlement activé
|
|
for guild_id_str, settings in all_settings.items():
|
|
if settings.get("rules_enabled", False) and settings.get("rules_accept_role_id"):
|
|
# Ajouter la view persistente
|
|
view = RulesAcceptButton(int(guild_id_str), settings["rules_accept_role_id"])
|
|
self.bot.add_view(view)
|
|
print(f"[Rules] View registered for guild {guild_id_str}")
|
|
|
|
self.persistent_views_added = True
|
|
except (json.JSONDecodeError, FileNotFoundError):
|
|
pass
|
|
|
|
@commands.Cog.listener()
|
|
async def on_ready(self):
|
|
"""Appelé quand le bot est prêt"""
|
|
print("🎫 Module Règles chargé avec succès!")
|
|
|
|
async def send_rules_message(self, channel: disnake.TextChannel, settings: dict) -> Optional[disnake.Message]:
|
|
"""Envoie le message du règlement dans un salon"""
|
|
title = settings.get("rules_title", "📜 Règlement du Serveur")
|
|
content = settings.get("rules_content", "")
|
|
accept_role_id = settings.get("rules_accept_role_id")
|
|
message_type = settings.get("rules_message_type", "embed") # Par défaut embed
|
|
|
|
if not accept_role_id:
|
|
return None
|
|
|
|
# Créer la view avec le bouton persistant
|
|
view = RulesAcceptButton(channel.guild.id, accept_role_id)
|
|
|
|
if message_type == "simple":
|
|
# Message simple
|
|
text_message = (
|
|
f"📜 **{title}**\n\n"
|
|
f"{content}\n\n"
|
|
"Bienvenue ! Pour accéder à l'ensemble des canaux du serveur, "
|
|
"vous devez accepter le règlement en cliquant sur le bouton ci-dessous.\n\n"
|
|
"En acceptant, vous confirmez avoir lu et compris les règles du serveur."
|
|
)
|
|
|
|
# Envoyer le message simple
|
|
try:
|
|
message = await channel.send(
|
|
content=text_message,
|
|
view=view
|
|
)
|
|
return message
|
|
except Exception as e:
|
|
print(f"Erreur lors de l'envoi du règlement simple: {e}")
|
|
return None
|
|
|
|
else: # embed
|
|
# Créer l'embed
|
|
embed = disnake.Embed(
|
|
title=title,
|
|
description=content,
|
|
color=0x2b2d31
|
|
)
|
|
|
|
embed.set_footer(text="Cliquez sur le bouton ci-dessous pour accepter le règlement")
|
|
|
|
# Envoyer seulement l'embed
|
|
try:
|
|
message = await channel.send(
|
|
embed=embed,
|
|
view=view
|
|
)
|
|
return message
|
|
except Exception as e:
|
|
print(f"Erreur lors de l'envoi du règlement embed: {e}")
|
|
return None
|
|
|
|
def format_default_rules(self, guild_name: str) -> tuple:
|
|
"""Retourne le titre et contenu par défaut du règlement"""
|
|
title = f"📜 Règlement de {guild_name}"
|
|
content = (
|
|
"Bienvenue sur notre serveur ! Avant de commencer à interagir avec la communauté, "
|
|
"veuille lire et accepter notre règlement.\n\n"
|
|
"**📌 Règles à respecter :**\n\n"
|
|
"1. **Respectez les autres membres** - Pas d'insultes, de harcèlement ou de discrimination.\n"
|
|
"2. **Pas de contenu inapproprié** - Aucun contenu NSFW, violent ou choquant.\n"
|
|
"3. **Pas de spam ou de flood** - Évitez les messages répétitifs ou les emojis excessifs.\n"
|
|
"4. **Pas de publicité** - Ne partagez pas de liens vers d'autres serveurs sans autorisation.\n"
|
|
"5. **Restez dans les salons appropriés** - Respectez la thématique de chaque canal.\n\n"
|
|
"**⚠️ Sanctions :**\n"
|
|
"Le non-respect de ces règles peut entraîner des avertissements, des mute temporaires, "
|
|
"ou un banissement définitif selon la gravité des infractions.\n\n"
|
|
"*En cliquant sur \"J'accepte\", vous confirmez avoir lu et accepter ce règlement.*"
|
|
)
|
|
return title, content
|
|
|
|
|
|
def setup(bot):
|
|
"""Setup du cog de règlement"""
|
|
cog = RulesCog(bot)
|
|
bot.add_cog(cog)
|
|
|