diff --git a/bot.py b/bot.py index e516439..2130bfe 100755 --- a/bot.py +++ b/bot.py @@ -4,10 +4,10 @@ from dotenv import load_dotenv # Charger les variables d'environnement AVANT tout le reste load_dotenv() -import discord +import disnake from commandes.ticket.utils.permissions import can_access_config from commandes.ticket.data.storage import get_storage -from discord.ext import commands +from disnake.ext import commands import asyncio from src.logger import kuby_logger, log_performance import logging @@ -25,12 +25,12 @@ except ValueError: kuby_logger.error(f"❌ APPLICATION_ID invalide : {application_id_raw}") application_id = None -# Configuration des intents Discord -intents = discord.Intents.default() +# Configuration des intents Disnake +intents = disnake.Intents.default() intents.message_content = True intents.members = True intents.voice_states = True -kuby_logger.debug(f"Discord intents configured: {intents}") +kuby_logger.debug(f"Disnake intents configured: {intents}") class MyBot(commands.Bot): def __init__(self): @@ -135,7 +135,7 @@ class MyBot(commands.Bot): # Réponse adaptée (Slash vs Message) msg = "❌ Vous n'êtes pas autorisé à utiliser cette commande. Veuillez demander à être ajouté à la whitelist." if ctx.interaction: - await ctx.interaction.response.send_message(msg, ephemeral=True) + await ctx.send(msg, ephemeral=True) else: await ctx.send(msg) return False @@ -147,8 +147,8 @@ class MyBot(commands.Bot): # Sync slash commands try: - await self.tree.sync() - kuby_logger.info("✅ Commandes slash synchronisées avec Discord") + # Disnake handles syncing automatically by default + kuby_logger.info("✅ Commandes slash prêtes avec Disnake") except Exception as e: kuby_logger.error(f"❌ Erreur lors de la synchronisation des commandes slash : {e}", exc_info=True) diff --git a/commandes/absence_staff.py b/commandes/absence_staff.py index 7d83cbf..53a79c3 100755 --- a/commandes/absence_staff.py +++ b/commandes/absence_staff.py @@ -1,6 +1,5 @@ -import discord -from discord.ext import commands, tasks -from discord import app_commands, ui +import disnake +from disnake.ext import commands, tasks import json import asyncio import os @@ -82,22 +81,26 @@ def parse_date(date_str: str): # --- MODALS & VIEWS --- -class AbsenceModal(ui.Modal, title="Déclarer une Absence Staff"): - motif = ui.TextInput( +class AbsenceModal(disnake.ui.Modal): + def __init__(self, superieur: Optional[disnake.Member] = None): + super().__init__(title="Déclarer une Absence Staff") + self.superieur = superieur + + motif = disnake.ui.TextInput( label="Motif de l'absence", - style=discord.TextStyle.paragraph, + style=disnake.TextStyle.paragraph, placeholder="Ex: Vacances, Raisons personnelles, Travail...", required=True, max_length=500 ) - date_debut = ui.TextInput( + date_debut = disnake.ui.TextInput( label="Début (JJ/MM/AAAA HH:MM)", placeholder="Ex: 15/05/2024 08:00", min_length=16, max_length=16, required=True ) - date_fin = ui.TextInput( + date_fin = disnake.ui.TextInput( label="Fin (JJ/MM/AAAA HH:MM)", placeholder="Ex: 20/05/2024 18:00", min_length=16, @@ -105,11 +108,7 @@ class AbsenceModal(ui.Modal, title="Déclarer une Absence Staff"): required=True ) - def __init__(self, superieur: Optional[discord.Member] = None): - super().__init__() - self.superieur = superieur - - async def on_submit(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.Interaction): try: debut = parse_date(self.date_debut.value) fin = parse_date(self.date_fin.value) @@ -182,20 +181,20 @@ class AbsenceModal(ui.Modal, title="Déclarer une Absence Staff"): await interaction.response.send_message("✅ Absence enregistrée avec succès !", ephemeral=True) -class AbsenceConfigView(ui.View): +class AbsenceConfigView(disnake.ui.View): def __init__(self, guild_id: int): super().__init__(timeout=60) self.guild_id = guild_id - @ui.button(label="Définir le Salon", style=discord.ButtonStyle.primary, emoji="📁") - async def set_channel(self, interaction: discord.Interaction, button: ui.Button): + @disnake.ui.button(label="Définir le Salon", style=disnake.ButtonStyle.primary, emoji="📁") + async def set_channel(self, interaction: disnake.Interaction, button: disnake.ui.Button): await interaction.response.send_message("Mentionne le salon ici (ou envoie son ID) :", ephemeral=True) def check(m): return m.author == interaction.user and m.channel == interaction.channel try: - msg = await interaction.client.wait_for('message', check=check, timeout=30) + msg = await interaction.bot.wait_for('message', check=check, timeout=30) channel = None if msg.channel_mentions: channel = msg.channel_mentions[0] @@ -214,15 +213,15 @@ class AbsenceConfigView(ui.View): except asyncio.TimeoutError: await interaction.followup.send("❌ Temps écoulé.", ephemeral=True) - @ui.button(label="Définir le Rôle", style=discord.ButtonStyle.primary, emoji="🎭") - async def set_role(self, interaction: discord.Interaction, button: ui.Button): + @disnake.ui.button(label="Définir le Rôle", style=disnake.ButtonStyle.primary, emoji="🎭") + async def set_role(self, interaction: disnake.Interaction, button: disnake.ui.Button): await interaction.response.send_message("Mentionne le rôle ici (ou envoie son ID) :", ephemeral=True) def check(m): return m.author == interaction.user and m.channel == interaction.channel try: - msg = await interaction.client.wait_for('message', check=check, timeout=30) + msg = await interaction.bot.wait_for('message', check=check, timeout=30) role = None if msg.role_mentions: role = msg.role_mentions[0] @@ -246,22 +245,22 @@ class Absence(commands.Cog): self.bot = bot self.check_absences.start() - @app_commands.command(name="absence", description="Déclarer une absence staff") - @app_commands.describe(superieur="Le supérieur hiérarchique à prévenir") - async def absence(self, interaction: discord.Interaction, superieur: Optional[discord.Member] = None): + @commands.slash_command(name="absence", description="Déclarer une absence staff") + async def absence(self, interaction: disnake.ApplicationCommandInteraction, + superieur: Optional[disnake.Member] = commands.Param(None, description="Le supérieur hiérarchique à prévenir")): """Déclare une absence via un formulaire""" await interaction.response.send_modal(AbsenceModal(superieur)) - @app_commands.command(name="absence_config", description="Configurer le système d'absence (Admin)") - @app_commands.checks.has_permissions(administrator=True) - async def absence_config(self, interaction: discord.Interaction): + @commands.slash_command(name="absence_config", description="Configurer le système d'absence (Admin)") + @commands.has_permissions(administrator=True) + async def absence_config(self, interaction: disnake.ApplicationCommandInteraction): """Ouvre le panneau de configuration des absences""" settings = load_settings(interaction.guild.id) - embed = discord.Embed( + embed = disnake.Embed( title="⚙️ Configuration des Absences Staff", description="Utilisez les boutons ci-dessous pour configurer le système.", - color=discord.Color.blue() + color=disnake.Color.blue() ) channel_id = settings.get("absence_channel_id") @@ -280,7 +279,7 @@ class Absence(commands.Cog): await interaction.response.send_message(embed=embed, view=AbsenceConfigView(interaction.guild.id), ephemeral=True) - async def strike_absence_message(self, guild: discord.Guild, channel_id: int, message_id: int): + async def strike_absence_message(self, guild: disnake.Guild, channel_id: int, message_id: int): """Modifie le message d'absence pour le barrer au lieu de le supprimer""" channel = guild.get_channel(channel_id) if not channel: @@ -311,8 +310,8 @@ class Absence(commands.Cog): except Exception as e: print(f"Erreur lors du barrage du message d'absence: {e}") - @app_commands.command(name="fin_absence", description="Terminer son absence staff") - async def fin_absence(self, interaction: discord.Interaction): + @commands.slash_command(name="fin_absence", description="Terminer son absence staff") + async def fin_absence(self, interaction: disnake.ApplicationCommandInteraction): """Met fin à l'absence en cours""" all_abs = await load_absences() guild_abs = get_guild_absences(all_abs, interaction.guild.id) @@ -343,8 +342,8 @@ class Absence(commands.Cog): await save_absences(all_abs) await interaction.response.send_message("✅ Ton absence a été retirée.", ephemeral=True) - @app_commands.command(name="liste_absences", description="Afficher la liste des absences staff en cours") - async def liste_absences(self, interaction: discord.Interaction): + @commands.slash_command(name="liste_absences", description="Afficher la liste des absences staff en cours") + async def liste_absences(self, interaction: disnake.ApplicationCommandInteraction): """Affiche les absences en cours sur le serveur""" all_abs = await load_absences() guild_abs = get_guild_absences(all_abs, interaction.guild.id) @@ -360,10 +359,10 @@ class Absence(commands.Cog): per_page = 5 pages = [absences[i:i + per_page] for i in range(0, len(absences), per_page)] - def create_embed(page_index: int) -> discord.Embed: - embed = discord.Embed( + def create_embed(page_index: int) -> disnake.Embed: + embed = disnake.Embed( title=f"📋 Absences staff - {interaction.guild.name} ({page_index + 1}/{len(pages)})", - color=discord.Color.orange() + color=disnake.Color.orange() ) for user_id, info in pages[page_index]: embed.add_field( @@ -378,19 +377,19 @@ class Absence(commands.Cog): embed.set_footer(text="Gestion des absences du staff") return embed - class PaginatorView(ui.View): + class PaginatorView(disnake.ui.View): def __init__(self): super().__init__(timeout=60) self.page = 0 - @ui.button(label="⬅️ Précédent", style=discord.ButtonStyle.secondary, disabled=True) - async def previous(self, interaction_btn: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="⬅️ Précédent", style=disnake.ButtonStyle.secondary, disabled=True) + async def previous(self, interaction_btn: disnake.Interaction, button: disnake.ui.Button): self.page -= 1 self.update_buttons() await interaction_btn.response.edit_message(embed=create_embed(self.page), view=self) - @ui.button(label="➡️ Suivant", style=discord.ButtonStyle.secondary, disabled=len(pages) == 1) - async def next(self, interaction_btn: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="➡️ Suivant", style=disnake.ButtonStyle.secondary, disabled=len(pages) == 1) + async def next(self, interaction_btn: disnake.Interaction, button: disnake.ui.Button): self.page += 1 self.update_buttons() await interaction_btn.response.edit_message(embed=create_embed(self.page), view=self) diff --git a/commandes/backup.py b/commandes/backup.py index 82c6a89..5cafb7c 100755 --- a/commandes/backup.py +++ b/commandes/backup.py @@ -7,9 +7,8 @@ import asyncio from datetime import datetime from typing import Dict, Any, List -import discord -from discord.ext import commands -from discord import app_commands +import disnake +from disnake.ext import commands BACKUPS_ROOT = "data/security_backups" # dossier de sortie des sauvegardes @@ -46,10 +45,10 @@ def estimate_duration_seconds( return int(meta_cost + msg_cost + base_overhead) -def serialize_overwrites(channel: discord.abc.GuildChannel) -> List[Dict[str, Any]]: +def serialize_overwrites(channel: disnake.abc.GuildChannel) -> List[Dict[str, Any]]: serialized: List[Dict[str, Any]] = [] for target, perms in channel.overwrites.items(): - target_type = "role" if isinstance(target, discord.Role) else "member" + target_type = "role" if isinstance(target, disnake.Role) else "member" serialized.append({ "target_type": target_type, "target_id": target.id, @@ -64,20 +63,17 @@ class Backup(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - backup_group = app_commands.Group(name="backup", description="Outils de sauvegarde du serveur") + @commands.slash_command(name="backup", description="Outils de sauvegarde du serveur") + async def backup_group(self, interaction: disnake.ApplicationCommandInteraction): + pass - @backup_group.command(name="create", description="Créer une sauvegarde du serveur") - @app_commands.describe( - save_messages="Inclure l'export des messages", - privacy_notice="Afficher un avertissement public si messages sauvegardés", - message_limit_per_channel="Nombre max de messages à exporter par salon (0 = tous)" - ) + @backup_group.sub_command(name="create", description="Créer une sauvegarde du serveur") async def backup_create( self, - interaction: discord.Interaction, - save_messages: bool = True, - privacy_notice: bool = True, - message_limit_per_channel: int = 10000 + interaction: disnake.ApplicationCommandInteraction, + save_messages: bool = commands.Param(True, description="Inclure l'export des messages"), + privacy_notice: bool = commands.Param(True, description="Afficher un avertissement public si messages sauvegardés"), + message_limit_per_channel: int = commands.Param(10000, description="Nombre max de messages à exporter par salon (0 = tous)") ): guild = interaction.guild assert guild is not None, "Cette commande doit être utilisée dans un serveur." @@ -93,20 +89,20 @@ class Backup(commands.Cog): ) return - # Déferre l'interaction - await interaction.response.defer(ephemeral=True, thinking=True) + # Deferre l'interaction + await interaction.response.defer(ephemeral=True) # Avertissement de confidentialité if privacy_notice and save_messages: try: await interaction.channel.send( - embed=discord.Embed( + embed=disnake.Embed( title="📢 Information de confidentialité (transparence)", description=( "Une **sauvegarde du serveur** est en cours et **inclus les messages**.\n" "Vous êtes dans l'obligation de prévenir les membres." ), - color=discord.Color.orange() + color=disnake.Color.orange() ) ) except Exception: @@ -132,14 +128,14 @@ class Backup(commands.Cog): message_limit_per_channel if message_limit_per_channel else 100000 ) - prog_embed = discord.Embed( + prog_embed = disnake.Embed( title="🗄️ Sauvegarde en cours", description=( f"**Serveur :** {guild.name} (`{guild.id}`)\n" f"**Estimation :** ~{estimation_s} secondes\n" f"**Options** : messages={'oui' if save_messages else 'non'} • limite/chan={message_limit_per_channel or 'tous'}" ), - color=discord.Color.blurple() + color=disnake.Color.blurple() ) prog_embed.add_field(name="Progression", value=progress_bar(0.0), inline=False) progress_msg = await interaction.followup.send(embed=prog_embed, ephemeral=False) @@ -173,11 +169,11 @@ class Backup(commands.Cog): chans_dump=[] for ch in guild.channels: base={"id":ch.id,"name":ch.name,"type":str(ch.type),"position":ch.position,"category_id":ch.category.id if getattr(ch,"category",None) else None,"overwrites":serialize_overwrites(ch)} - if isinstance(ch,discord.TextChannel): + if isinstance(ch,disnake.TextChannel): base.update({"topic":ch.topic,"nsfw":ch.nsfw,"slowmode_delay":ch.slowmode_delay,"default_auto_archive_duration":ch.default_auto_archive_duration}) - elif isinstance(ch,discord.VoiceChannel): + elif isinstance(ch,disnake.VoiceChannel): base.update({"bitrate":ch.bitrate,"user_limit":ch.user_limit,"rtc_region":str(ch.rtc_region) if ch.rtc_region else None}) - elif isinstance(ch,discord.ForumChannel): + elif isinstance(ch,disnake.ForumChannel): base.update({"nsfw":ch.nsfw,"default_auto_archive_duration":ch.default_auto_archive_duration}) chans_dump.append(base) with open(os.path.join(base_dir,"channels.json"),"w",encoding="utf-8") as f: @@ -208,7 +204,7 @@ class Backup(commands.Cog): data={"id":msg.id,"channel_id":msg.channel.id,"author_id":msg.author.id,"author_bot":msg.author.bot,"created_at":msg.created_at.isoformat(),"content":msg.content,"attachments":[a.url for a in msg.attachments],"embeds":[e.to_dict() for e in msg.embeds],"reference":{"message_id":getattr(msg.reference,"message_id",None),"channel_id":getattr(msg.reference,"channel_id",None)} if msg.reference else None} f.write(json.dumps(data,ensure_ascii=False)+"\n") count+=1 - except discord.Forbidden: pass + except disnake.Forbidden: pass except Exception: pass exported_total+=count done+=per_chan_weight @@ -226,7 +222,7 @@ class Backup(commands.Cog): rel=os.path.relpath(full,base_dir) zf.write(full,rel) except Exception as exc: - prog_embed.color=discord.Color.red() + prog_embed.color=disnake.Color.red() prog_embed.set_field_at(0,name="Progression",value=progress_bar(1.0),inline=False) prog_embed.set_footer(text=f"Erreur lors de la création du ZIP : {exc}") await progress_msg.edit(embed=prog_embed) @@ -234,15 +230,15 @@ class Backup(commands.Cog): return # Finalisation - prog_embed.color=discord.Color.green() + prog_embed.color=disnake.Color.green() prog_embed.set_field_at(0,name="Progression",value=progress_bar(1.0),inline=False) prog_embed.add_field(name="Archive",value=f"`{zip_path}`",inline=False) prog_embed.set_footer(text="Sauvegarde terminée ✅") await progress_msg.edit(embed=prog_embed) await interaction.followup.send("🎉 Sauvegarde terminée. Un message de progression avec le chemin de l’archive a été publié.", ephemeral=True) - @backup_group.command(name="list", description="Lister les archives de sauvegarde disponibles") - async def backup_list(self, interaction: discord.Interaction): + @backup_group.sub_command(name="list", description="Lister les archives de sauvegarde disponibles") + async def backup_list(self, interaction: disnake.ApplicationCommandInteraction): guild = interaction.guild assert guild is not None, "Cette commande doit être utilisée dans un serveur." @@ -256,7 +252,7 @@ class Backup(commands.Cog): ) return - await interaction.response.defer(ephemeral=True, thinking=True) + await interaction.response.defer(ephemeral=True) ensure_dir(BACKUPS_ROOT) files = [f for f in os.listdir(BACKUPS_ROOT) if f.endswith(".zip")] files.sort(reverse=True) @@ -266,7 +262,7 @@ class Backup(commands.Cog): return desc="\n".join(f"• `{name}`" for name in files[:20]) - embed=discord.Embed(title="📦 Archives disponibles",description=desc,color=discord.Color.blurple()) + embed=disnake.Embed(title="📦 Archives disponibles",description=desc,color=disnake.Color.blurple()) if len(files)>20: embed.set_footer(text=f"... et {len(files)-20} de plus") await interaction.followup.send(embed=embed,ephemeral=True) diff --git a/commandes/backup_restore.py b/commandes/backup_restore.py index 14670d4..d7f69cb 100755 --- a/commandes/backup_restore.py +++ b/commandes/backup_restore.py @@ -5,9 +5,8 @@ import zipfile import tempfile import shutil from typing import List -import discord -from discord import app_commands -from discord.ext import commands +import disnake +from disnake.ext import commands BACKUPS_ROOT = "data/security_backups" @@ -43,7 +42,7 @@ class BackupRestore(commands.Cog): elements.append(base) return elements - async def element_autocomplete(self, interaction: discord.Interaction, current: str): + async def element_autocomplete(self, interaction: disnake.ApplicationCommandInteraction, current: str): """Complétion automatique pour l'option 'element'.""" guild = interaction.guild if guild is None: @@ -60,9 +59,9 @@ class BackupRestore(commands.Cog): backup_path = os.path.join(BACKUPS_ROOT, last_backup) elements = await self.list_backup_elements(backup_path) - return [app_commands.Choice(name=e, value=e) for e in elements if current.lower() in e.lower()][:25] + return [e for e in elements if current.lower() in e.lower()][:25] - async def restore_roles(self, guild: discord.Guild, backup_path: str): + async def restore_roles(self, guild: disnake.Guild, backup_path: str): """Restaure les rôles à partir du backup.""" roles_file = os.path.join(backup_path, "roles.json") if os.path.isfile(roles_file): @@ -72,16 +71,16 @@ class BackupRestore(commands.Cog): try: await guild.create_role( name=r["name"], - permissions=discord.Permissions(r["permissions"]), - color=discord.Color(r.get("color", 0)), + permissions=disnake.Permissions(r["permissions"]), + color=disnake.Color(r.get("color", 0)), hoist=r.get("hoist", False), mentionable=r.get("mentionable", False), reason="Restauration backup" ) - except discord.Forbidden: + except disnake.Forbidden: continue - async def restore_channels(self, guild: discord.Guild, backup_path: str): + async def restore_channels(self, guild: disnake.Guild, backup_path: str): """Restaure les channels à partir du backup.""" channels_file = os.path.join(backup_path, "channels.json") if os.path.isfile(channels_file): @@ -105,10 +104,10 @@ class BackupRestore(commands.Cog): user_limit=c.get("user_limit", 0), reason="Restauration backup" ) - except discord.Forbidden: + except disnake.Forbidden: continue - async def restore_messages(self, channel: discord.TextChannel, backup_path: str): + async def restore_messages(self, channel: disnake.TextChannel, backup_path: str): """Restaure les messages à partir du backup (solution ZIP incluse).""" if backup_path.endswith(".zip"): temp_dir = tempfile.mkdtemp() @@ -133,17 +132,12 @@ class BackupRestore(commands.Cog): for msg in messages: await channel.send(msg["content"]) - @app_commands.command(name="granulaire", description="Restaure un élément précis d'une sauvegarde") - @app_commands.describe( - backup="Nom ou ID de la sauvegarde", - element="Élément à restaurer (roles, channels, messages...)" - ) - @app_commands.autocomplete(element=element_autocomplete) + @commands.slash_command(name="granulaire", description="Restaure un élément précis d'une sauvegarde") async def restore_granulaire( self, - interaction: discord.Interaction, - backup: str, - element: str + interaction: disnake.ApplicationCommandInteraction, + backup: str = commands.Param(description="Nom ou ID de la sauvegarde"), + element: str = commands.Param(description="Élément à restaurer (roles, channels, messages...)") ): guild = interaction.guild if guild is None: @@ -162,7 +156,7 @@ class BackupRestore(commands.Cog): await interaction.response.send_message(f"❌ La sauvegarde `{backup}` est introuvable.", ephemeral=True) return - await interaction.response.defer(ephemeral=True, thinking=True) + await interaction.response.defer(ephemeral=True) available_elements = await self.list_backup_elements(backup_path) if element not in available_elements: @@ -200,5 +194,9 @@ class BackupRestore(commands.Cog): ephemeral=True ) + @restore_granulaire.autocomplete("element") + async def element_autocomplete_decorator(self, interaction: disnake.ApplicationCommandInteraction, current: str): + return await self.element_autocomplete(interaction, current) + async def setup(bot: commands.Bot): await bot.add_cog(BackupRestore(bot)) diff --git a/commandes/blacklist.py b/commandes/blacklist.py index cc53e5e..a64f0b0 100755 --- a/commandes/blacklist.py +++ b/commandes/blacklist.py @@ -1,6 +1,5 @@ -import discord -from discord.ext import commands, tasks -from discord import app_commands +import disnake +from disnake.ext import commands, tasks import json import os import asyncio @@ -57,8 +56,8 @@ class Blacklist(commands.Cog): agents = load_json(AGENTS_FILE, {"agents": []}) return str(user_id) in agents["agents"] - @app_commands.command(name="blacklist", description="Blacklist un utilisateur") - async def blacklist(self, interaction: discord.Interaction, user: discord.User, reason: str = "Aucune raison fournie"): + @commands.slash_command(name="blacklist", description="Blacklist un utilisateur") + async def blacklist(self, interaction: disnake.ApplicationCommandInteraction, user: disnake.User, reason: str = "Aucune raison fournie"): if not self.is_agent(interaction.user.id): return await interaction.response.send_message("⛔ Tu n’as pas la permission.", ephemeral=True) @@ -70,7 +69,7 @@ class Blacklist(commands.Cog): save_json(BLACKLIST_FILE, blacklist) # Préparer l'embed pro - embed = discord.Embed( + embed = disnake.Embed( title="🚫 Mise en blacklist - Réseau Omega Kube", description=( f"Bonjour {user.mention},\n\n" @@ -82,7 +81,7 @@ class Blacklist(commands.Cog): "📩 Vous disposez de **7 jours** pour contester cette décision en ouvrant un ticket via le bouton ci-dessous.\n\n" "⏳ Passé ce délai, vous serez **définitivement banni de tout le réseau**, sans autre recours possible." ), - color=discord.Color.red() + color=disnake.Color.red() ) embed.set_footer(text="Omega Kube - Nixfix06 & Mgstudios") @@ -122,8 +121,8 @@ class Blacklist(commands.Cog): ephemeral=True ) - @app_commands.command(name="unblacklist", description="Retirer un utilisateur de la blacklist") - async def unblacklist(self, interaction: discord.Interaction, user: discord.User): + @commands.slash_command(name="unblacklist", description="Retirer un utilisateur de la blacklist") + async def unblacklist(self, interaction: disnake.ApplicationCommandInteraction, user: disnake.User): if not self.is_agent(interaction.user.id): return await interaction.response.send_message("⛔ Tu n’as pas la permission.", ephemeral=True) @@ -163,21 +162,21 @@ class Blacklist(commands.Cog): save_json(BLACKLIST_FILE, blacklist) @commands.Cog.listener() - async def on_message(self, message: discord.Message): + async def on_message(self, message: disnake.Message): if message.author.bot: return tickets = load_json(TICKETS_FILE, {}) - if isinstance(message.channel, discord.DMChannel): + if isinstance(message.channel, disnake.DMChannel): user_id = str(message.author.id) if user_id in tickets: guild = self.bot.get_guild(MAIN_GUILD_ID) ticket_channel = guild.get_channel(tickets[user_id]["channel_id"]) if ticket_channel: - embed = discord.Embed( + embed = disnake.Embed( description=message.content, - color=discord.Color.blue() + color=disnake.Color.blue() ).set_author(name=message.author, icon_url=message.author.display_avatar.url) await ticket_channel.send(embed=embed) @@ -186,22 +185,22 @@ class Blacklist(commands.Cog): if message.channel.id == data["channel_id"] and self.is_agent(message.author.id): user = await self.bot.fetch_user(int(user_id)) try: - embed = discord.Embed( + embed = disnake.Embed( description=message.content, - color=discord.Color.green() + color=disnake.Color.green() ).set_author(name=f"Agent {message.author}", icon_url=message.author.display_avatar.url) await user.send(embed=embed) except: pass # --------- Boutons --------- -class TicketButton(discord.ui.View): +class TicketButton(disnake.ui.View): def __init__(self, user_id: int): super().__init__(timeout=None) self.user_id = user_id - @discord.ui.button(label="📩 Ouvrir une demande de révision", style=discord.ButtonStyle.primary, custom_id="blacklist_open_ticket") - async def open_ticket(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="📩 Ouvrir une demande de révision", style=disnake.ButtonStyle.primary, custom_id="blacklist_open_ticket") + async def open_ticket(self, interaction: disnake.Interaction, button: disnake.ui.Button): if interaction.user.id != self.user_id: return await interaction.response.send_message("❌ Ce bouton ne t’appartient pas.", ephemeral=True) @@ -209,7 +208,7 @@ class TicketButton(discord.ui.View): if str(self.user_id) in tickets: return await interaction.response.send_message("❌ Tu as déjà un ticket ouvert.", ephemeral=True) - guild = interaction.client.get_guild(MAIN_GUILD_ID) + guild = interaction.bot.get_guild(MAIN_GUILD_ID) category = guild.get_channel(TICKET_CATEGORY_ID) ticket_channel = await guild.create_text_channel( @@ -222,10 +221,10 @@ class TicketButton(discord.ui.View): save_json(TICKETS_FILE, tickets) view_close = CloseTicketView(self.user_id) - embed = discord.Embed( + embed = disnake.Embed( title="📩 Ticket ouvert", description=f"Un utilisateur blacklisté (<@{self.user_id}>) a ouvert une demande de révision.\n\nUn agent Omega Kube prendra en charge ce dossier.", - color=discord.Color.blurple() + color=disnake.Color.blurple() ) await ticket_channel.send(embed=embed, view=view_close) @@ -234,27 +233,27 @@ class TicketButton(discord.ui.View): button.disabled = True self.stop() -class CloseTicketView(discord.ui.View): +class CloseTicketView(disnake.ui.View): def __init__(self, user_id: int): super().__init__(timeout=None) self.user_id = user_id - @discord.ui.button(label="❌ Fermer le ticket", style=discord.ButtonStyle.danger, custom_id="blacklist_close_ticket") - async def close_ticket(self, interaction: discord.Interaction, button: discord.ui.Button): - if not interaction.client.get_cog("Blacklist").is_agent(interaction.user.id): + @disnake.ui.button(label="❌ Fermer le ticket", style=disnake.ButtonStyle.danger, custom_id="blacklist_close_ticket") + async def close_ticket(self, interaction: disnake.Interaction, button: disnake.ui.Button): + if not interaction.bot.get_cog("Blacklist").is_agent(interaction.user.id): return await interaction.response.send_message("⛔ Seuls les agents peuvent fermer ce ticket.", ephemeral=True) view = ConfirmCloseView(self.user_id) await interaction.response.send_message("⚠️ Veux-tu vraiment fermer ce ticket ?", view=view, ephemeral=True) -class ConfirmCloseView(discord.ui.View): +class ConfirmCloseView(disnake.ui.View): def __init__(self, user_id: int): super().__init__(timeout=30) self.user_id = user_id - @discord.ui.button(label="✅ Oui", style=discord.ButtonStyle.success, custom_id="blacklist_confirm_close") - async def confirm(self, interaction: discord.Interaction, button: discord.ui.Button): - if not interaction.client.get_cog("Blacklist").is_agent(interaction.user.id): + @disnake.ui.button(label="✅ Oui", style=disnake.ButtonStyle.success, custom_id="blacklist_confirm_close") + async def confirm(self, interaction: disnake.Interaction, button: disnake.ui.Button): + if not interaction.bot.get_cog("Blacklist").is_agent(interaction.user.id): return await interaction.response.send_message("⛔ Tu n’as pas la permission.", ephemeral=True) tickets = load_json(TICKETS_FILE, {}) @@ -268,19 +267,19 @@ class ConfirmCloseView(discord.ui.View): del tickets[str(self.user_id)] save_json(TICKETS_FILE, tickets) - user = await interaction.client.fetch_user(int(self.user_id)) + user = await interaction.bot.fetch_user(int(self.user_id)) try: - embed = discord.Embed( + embed = disnake.Embed( title="🔒 Ticket fermé", description="Ton ticket a été examiné et fermé.\n\n⚠️ Après analyse, la sanction est confirmée et tu restes blacklisté de l’ensemble du réseau.", - color=discord.Color.orange() + color=disnake.Color.orange() ) await user.send(embed=embed) await asyncio.sleep(2) except: pass - for guild in interaction.client.guilds: + for guild in interaction.bot.guilds: member = guild.get_member(self.user_id) if member: try: @@ -290,8 +289,8 @@ class ConfirmCloseView(discord.ui.View): await interaction.response.send_message("✅ Ticket fermé avec succès.", ephemeral=True) - @discord.ui.button(label="❌ Non", style=discord.ButtonStyle.secondary, custom_id="blacklist_cancel_close") - async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="❌ Non", style=disnake.ButtonStyle.secondary, custom_id="blacklist_cancel_close") + async def cancel(self, interaction: disnake.Interaction, button: disnake.ui.Button): await interaction.response.send_message("❌ Fermeture annulée.", ephemeral=True) # --------- Setup --------- diff --git a/commandes/bug_report.py b/commandes/bug_report.py index 925246c..6bbc736 100644 --- a/commandes/bug_report.py +++ b/commandes/bug_report.py @@ -1,6 +1,5 @@ -import discord -from discord import app_commands -from discord.ext import commands +import disnake +from disnake.ext import commands from utils.gitlab_client import gitlab_client from aiohttp import web import logging @@ -33,27 +32,27 @@ def save_report(issue_iid, user_id): except Exception as e: kuby_logger.error(f"Error saving report to JSON: {e}") -class BugReportModal(discord.ui.Modal, title="Signaler un Bug"): +class BugReportModal(disnake.ui.Modal): def __init__(self, priority_choice): - super().__init__() + super().__init__(title="Signaler un Bug") self.priority_choice = priority_choice - bug_title = discord.ui.TextInput( + bug_title = disnake.ui.TextInput( label="Titre du bug", placeholder="Décrivez brièvement le problème...", required=True, max_length=100 ) - description = discord.ui.TextInput( + description = disnake.ui.TextInput( label="Description détaillée", - style=discord.TextStyle.paragraph, + style=disnake.TextStyle.paragraph, placeholder="Que s'est-il passé ? Comment reproduire le bug ?", required=True, max_length=1000 ) - async def on_submit(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.Interaction): await interaction.response.send_message("Envoi de votre rapport de bug à GitLab...", ephemeral=True) description_text = f"**Rapporté par:** {interaction.user} ({interaction.user.id})\n" @@ -76,23 +75,26 @@ class BugReportModal(discord.ui.Modal, title="Signaler un Bug"): else: await interaction.edit_original_response(content="❌ Une erreur est survenue lors de l'envoi du rapport à GitLab. Veuillez contacter un administrateur.") -class FeatureSuggestionModal(discord.ui.Modal, title="Suggérer une fonctionnalité"): - suggestion_title = discord.ui.TextInput( +class FeatureSuggestionModal(disnake.ui.Modal): + def __init__(self): + super().__init__(title="Suggérer une fonctionnalité") + + suggestion_title = disnake.ui.TextInput( label="Titre de la suggestion", placeholder="Que voulez-vous ajouter ?", required=True, max_length=100 ) - description = discord.ui.TextInput( + description = disnake.ui.TextInput( label="Détails de la fonctionnalité", - style=discord.TextStyle.paragraph, + style=disnake.TextStyle.paragraph, placeholder="Décrivez comment cela devrait fonctionner...", required=True, max_length=1000 ) - async def on_submit(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.Interaction): await interaction.response.send_message("Envoi de votre suggestion à GitLab...", ephemeral=True) description_text = f"**Suggéré par:** {interaction.user} ({interaction.user.id})\n\n" @@ -231,10 +233,10 @@ class BugReport(commands.Cog): try: # Création de l'embed pour la mise à jour de statut - embed = discord.Embed( + embed = disnake.Embed( title="🛠️ Mise à jour de votre signalement !", description=f"Le statut de votre rapport **{issue_title}** a été mis à jour par l'équipe.", - color=discord.Color.blue() + color=disnake.Color.blue() ) embed.add_field(name="Nouveaux Labels / Statuts", value=current_labels_str) @@ -244,7 +246,7 @@ class BugReport(commands.Cog): kuby_logger.info(f"Notification de label envoyée à {user} ({user.id}) pour l'issue #{issue_iid}") if dev and dev.id != user.id: await dev.send(f"✅ [SUIVI] L'utilisateur {user} ({user.id}) a bien reçu la notification de statut **{current_labels_str}** pour l'issue #{issue_iid} ({issue_title}).") - except discord.Forbidden: + except disnake.Forbidden: kuby_logger.warning(f"Impossible d'envoyer un MP à {user} ({user.id}) - MPs désactivés.") if dev and dev.id != user.id: await dev.send(f"⚠️ [DM BLOQUÉ] {user} ({user.id}) a ses MPs fermés. Impossible de notifier pour l'issue #{issue_iid}.") @@ -259,10 +261,10 @@ class BugReport(commands.Cog): if is_closing_now: try: - embed = discord.Embed( + embed = disnake.Embed( title="🛠️ Bug / Suggestion Terminé(e) !", description=f"Bonne nouvelle ! Votre signalement **{issue_title}** a été marqué comme terminé ou résolu.", - color=discord.Color.green() + color=disnake.Color.green() ) embed.add_field(name="Prochaine étape", value="La mise à jour arrive prochainement (ou est déjà là) !") embed.set_footer(text="Merci pour votre aide !") @@ -272,7 +274,7 @@ class BugReport(commands.Cog): kuby_logger.info(f"Notification de clôture envoyée à {user} ({user.id}) pour l'issue #{issue_iid}") if dev and dev.id != user.id: await dev.send(f"✅ [SUIVI] L'utilisateur {user} ({user.id}) a bien reçu la notification de CLÔTURE pour l'issue #{issue_iid}.") - except discord.Forbidden: + except disnake.Forbidden: kuby_logger.warning(f"Impossible d'envoyer le MP de clôture à {user} ({user.id}).") if dev and dev.id != user.id: await dev.send(f"⚠️ [DM BLOQUÉ] Impossible d'annoncer la clôture à {user} ({user.id}) pour l'issue #{issue_iid} (DMs désactivés).") @@ -290,10 +292,10 @@ class BugReport(commands.Cog): author_name = payload.get("user", {}).get("name", "Développeur") try: - embed = discord.Embed( + embed = disnake.Embed( title="💬 Nouveau commentaire sur votre signalement", description=f"Le développeur a répondu à votre rapport **{issue_title}** :\n\n>>> {note_body}", - color=discord.Color.orange() + color=disnake.Color.orange() ) embed.set_footer(text=f"Par {author_name}") @@ -302,7 +304,7 @@ class BugReport(commands.Cog): kuby_logger.info(f"Commentaire GitLab envoyé à {user} ({user.id}) pour l'issue #{issue_iid}") if dev and dev.id != user.id: await dev.send(f"✅ [SUIVI] L'utilisateur {user} ({user.id}) a bien reçu votre commentaire pour l'issue #{issue_iid}.") - except discord.Forbidden: + except disnake.Forbidden: kuby_logger.warning(f"Impossible d'envoyer le commentaire en MP à {user} ({user.id}).") if dev and dev.id != user.id: await dev.send(f"⚠️ [DM BLOQUÉ] Impossible d'envoyer votre commentaire à {user} ({user.id}) pour l'issue #{issue_iid} (DMs désactivés).") @@ -317,18 +319,19 @@ class BugReport(commands.Cog): kuby_logger.error(f"Error handling bot event: {e}") return web.json_response({"status": "error", "message": str(e)}, status=500) - @app_commands.command(name="signaler_bug", description="Signaler un bug aux développeurs") - @app_commands.choices(priority=[ - app_commands.Choice(name="Basse", value="Low"), - app_commands.Choice(name="Normale", value="Normal"), - app_commands.Choice(name="Haute", value="High"), - app_commands.Choice(name="Urgente", value="Urgent"), - ]) - async def signaler_bug(self, interaction: discord.Interaction, priority: app_commands.Choice[str]): - await interaction.response.send_modal(BugReportModal(priority)) + @commands.slash_command(name="signaler_bug", description="Signaler un bug aux développeurs") + async def signaler_bug(self, interaction: disnake.ApplicationCommandInteraction, + priority: str = commands.Param(description="Priorité du bug", choices={"Basse": "Low", "Normale": "Normal", "Haute": "High", "Urgente": "Urgent"})): + # Simulate app_commands.Choice behavior for existing code + from collections import namedtuple + Choice = namedtuple('Choice', ['name', 'value']) + # find the name from the value + p_name = [k for k, v in {"Basse": "Low", "Normale": "Normal", "Haute": "High", "Urgente": "Urgent"}.items() if v == priority][0] + choice = Choice(name=p_name, value=priority) + await interaction.response.send_modal(BugReportModal(choice)) - @app_commands.command(name="suggerer_fonctionnalite", description="Proposer une nouvelle fonctionnalité pour le bot") - async def suggerer_fonctionnalite(self, interaction: discord.Interaction): + @commands.slash_command(name="suggerer_fonctionnalite", description="Proposer une nouvelle fonctionnalité pour le bot") + async def suggerer_fonctionnalite(self, interaction: disnake.ApplicationCommandInteraction): await interaction.response.send_modal(FeatureSuggestionModal()) async def setup(bot): diff --git a/commandes/convocation.py b/commandes/convocation.py index d38f884..bb3921f 100755 --- a/commandes/convocation.py +++ b/commandes/convocation.py @@ -1,6 +1,5 @@ -import discord -from discord.ext import commands -from discord import app_commands +import disnake +from disnake.ext import commands import json import os from typing import List, Optional, Dict @@ -52,20 +51,17 @@ class Convocation(commands.Cog): del self.backups[key] save_json(CONV_BACKUPS_FILE, self.backups) - @commands.hybrid_group(name="convocation", description="Gère les convocations de membres") + @commands.slash_command(name="convocation", description="Gère les convocations de membres") @commands.has_permissions(manage_roles=True) - async def convocation_group(self, ctx): - if ctx.invoked_subcommand is None: - await ctx.send_help(ctx.command) + async def convocation_group(self, interaction: disnake.ApplicationCommandInteraction): + pass - @convocation_group.command(name="convoquer", description="Convoque un membre et verrouille ses accès") - @app_commands.describe( - membre="Le membre à convoquer", - raison="La raison de la convocation", - heure="L'heure de la convocation (ex: 14:30)" - ) - async def convoquer(self, ctx, membre: discord.Member, raison: str, heure: str): - guild_id = ctx.guild.id + @convocation_group.sub_command(name="convoquer", description="Convoque un membre et verrouille ses accès") + async def convoquer(self, interaction: disnake.ApplicationCommandInteraction, + membre: disnake.Member = commands.Param(description="Le membre à convoquer"), + raison: str = commands.Param(description="La raison de la convocation"), + heure: str = commands.Param(description="L'heure de la convocation (ex: 14:30)")): + guild_id = interaction.guild.id settings = self.get_guild_settings(guild_id) role_conv_id = settings.get("role_id") log_channel_id = settings.get("log_channel_id") @@ -73,30 +69,30 @@ class Convocation(commands.Cog): admin_ids = settings.get("admin_ids", []) if not role_conv_id: - await ctx.send("❌ Le rôle de convocation n'est pas configuré. Utilisez `/convocation config`.", ephemeral=True) + await interaction.response.send_message("❌ Le rôle de convocation n'est pas configuré. Utilisez `/convocation config`.", ephemeral=True) return - await ctx.defer(ephemeral=True) + await interaction.response.defer(ephemeral=True) try: # --- BACKUP --- - bot_top_role = ctx.guild.me.top_role + bot_top_role = interaction.guild.me.top_role backup_data = { "roles": [r.id for r in membre.roles if not r.is_default() and r.id != role_conv_id and r.position < bot_top_role.position], "overrides": {} } # 1. Backup and Remove current roles - current_roles = [ctx.guild.get_role(rid) for rid in backup_data["roles"] if ctx.guild.get_role(rid)] + current_roles = [interaction.guild.get_role(rid) for rid in backup_data["roles"] if interaction.guild.get_role(rid)] if current_roles: try: - await membre.remove_roles(*current_roles, reason=f"Convocation par {ctx.author}") - except discord.Forbidden: - await ctx.send("⚠️ Je n'ai pas pu retirer certains rôles (Permission refusée).", ephemeral=True) + await membre.remove_roles(*current_roles, reason=f"Convocation par {interaction.author}") + except disnake.Forbidden: + await interaction.followup.send("⚠️ Je n'ai pas pu retirer certains rôles (Permission refusée).", ephemeral=True) # 2. Dynamic Lockdown: Loop through ALL channels lockdown_count = 0 - for channel in ctx.guild.channels: + for channel in interaction.guild.channels: # On ignore le salon d'attente et ses parents s'il y en a if attente_id and (channel.id == attente_id or (hasattr(channel, 'category') and channel.category and channel.category.id == attente_id)): continue @@ -112,28 +108,28 @@ class Convocation(commands.Cog): # Specific override for this member: View Channel = False await channel.set_permissions(membre, view_channel=False, reason="Convocation Lockdown") lockdown_count += 1 - except discord.Forbidden: + except disnake.Forbidden: pass # Skip if we can't touch this channel self.save_backup(guild_id, membre.id, backup_data) # 3. Add Convocation role - role_conv = ctx.guild.get_role(role_conv_id) + role_conv = interaction.guild.get_role(role_conv_id) if role_conv: if role_conv.position >= bot_top_role.position: - await ctx.send(f"⚠️ Je ne peux pas donner le rôle {role_conv.mention} car il est plus haut que moi !", ephemeral=True) + await interaction.followup.send(f"⚠️ Je ne peux pas donner le rôle {role_conv.mention} car il est plus haut que moi !", ephemeral=True) else: try: await membre.add_roles(role_conv, reason="Début de convocation") - except discord.Forbidden: - await ctx.send(f"⚠️ Impossible de donner le rôle {role_conv.name} (Permission refusée).", ephemeral=True) + except disnake.Forbidden: + await interaction.followup.send(f"⚠️ Impossible de donner le rôle {role_conv.name} (Permission refusée).", ephemeral=True) # 4. Embeds & Logs (Inchangés mais inclus pour la cohérence) - embed_conv = discord.Embed( + embed_conv = disnake.Embed( title="CONVOCATION | 🔖", - description=f"> Bonjour {membre.mention}, vous êtes **convoqué** par {ctx.author.mention}. Voici les **détails** :", + description=f"> Bonjour {membre.mention}, vous êtes **convoqué** par {interaction.author.mention}. Voici les **détails** :", color=0x0099ff, - timestamp=discord.utils.utcnow() + timestamp=disnake.utils.utcnow() ) embed_conv.add_field(name="**Raison de la convocation :**", value=f"➜ {raison}", inline=False) embed_conv.add_field(name="**Heure de la convocation :**", value=f"➜ {heure}", inline=False) @@ -141,15 +137,15 @@ class Convocation(commands.Cog): "> Merci de vous présenter dans le vocal attente **5 minutes** avant l'heure.\n\n" "> La **levée** de suspension se fera uniquement à la **fin** de la convocation." ), inline=False) - embed_conv.set_footer(text=f"Gestion de {ctx.guild.name}") + embed_conv.set_footer(text=f"Gestion de {interaction.guild.name}") - embed_log = discord.Embed( + embed_log = disnake.Embed( title="📋 CONVOCATION ENVOYÉE", color=0xff9900, - timestamp=discord.utils.utcnow() + timestamp=disnake.utils.utcnow() ) embed_log.add_field(name="👤 Membre convoqué", value=f"{membre.mention} ({membre.id})", inline=True) - embed_log.add_field(name="👮 Convoqué par", value=f"{ctx.author.mention}", inline=True) + embed_log.add_field(name="👮 Convoqué par", value=f"{interaction.author.mention}", inline=True) embed_log.add_field(name="📄 Raison", value=raison, inline=False) embed_log.add_field(name="🕒 Heure", value=heure, inline=False) @@ -158,7 +154,7 @@ class Convocation(commands.Cog): try: await membre.send(embed=embed_conv) dm_sent = True - except discord.Forbidden: + except disnake.Forbidden: dm_sent = False # Admin & Channel Logs @@ -169,47 +165,47 @@ class Convocation(commands.Cog): except: pass if log_channel_id: - chan = ctx.guild.get_channel(log_channel_id) + chan = interaction.guild.get_channel(log_channel_id) if chan: await chan.send(embed=embed_log) - await ctx.send(f"✅ Convocation envoyée à {membre.mention}. ({lockdown_count} salons verrouillés) " + + await interaction.followup.send(f"✅ Convocation envoyée à {membre.mention}. ({lockdown_count} salons verrouillés) " + ("" if dm_sent else "(⚠️ DM impossible)"), ephemeral=True) except Exception as e: import traceback traceback.print_exc() - await ctx.send(f"❌ Une erreur critique est survenue : {e}", ephemeral=True) + await interaction.followup.send(f"❌ Une erreur critique est survenue : {e}", ephemeral=True) - @convocation_group.command(name="lever", description="Lève la convocation d'un membre et restaure ses accès") - @app_commands.describe(membre="Le membre à libérer") - async def lever(self, ctx, membre: discord.Member): - guild_id = ctx.guild.id + @convocation_group.sub_command(name="lever", description="Lève la convocation d'un membre et restaure ses accès") + async def lever(self, interaction: disnake.ApplicationCommandInteraction, + membre: disnake.Member = commands.Param(description="Le membre à libérer")): + guild_id = interaction.guild.id settings = self.get_guild_settings(guild_id) role_conv_id = settings.get("role_id") - await ctx.defer(ephemeral=True) + await interaction.response.defer(ephemeral=True) backup_data = self.get_backup(guild_id, membre.id) if not backup_data: - await ctx.send("⚠️ Aucune donnée de convocation trouvée pour ce membre.", ephemeral=True) + await interaction.followup.send("⚠️ Aucune donnée de convocation trouvée pour ce membre.", ephemeral=True) return # 1. Remove Convocation role if role_conv_id: - role_conv = ctx.guild.get_role(role_conv_id) + role_conv = interaction.guild.get_role(role_conv_id) if role_conv and role_conv in membre.roles: await membre.remove_roles(role_conv, reason="Fin de convocation") # 2. Restore channel overrides restore_count = 0 overrides = backup_data.get("overrides", {}) - for channel in ctx.guild.channels: + for channel in interaction.guild.channels: chan_id_str = str(channel.id) try: if chan_id_str in overrides: # Restore previous override ov_data = overrides[chan_id_str] - overwrite = discord.PermissionOverwrite(**ov_data) + overwrite = disnake.PermissionOverwrite(**ov_data) await channel.set_permissions(membre, overwrite=overwrite, reason="Restauration fin de convocation") restore_count += 1 else: @@ -217,7 +213,7 @@ class Convocation(commands.Cog): # On ne vide que si un override membre existait (sinon on risque de supprimer des perms rôles ?) # En fait set_permissions(member, overwrite=None) supprime l'entrée PERSONNELLE du membre. await channel.set_permissions(membre, overwrite=None, reason="Fin de convocation - Cleanup") - except discord.Forbidden: + except disnake.Forbidden: pass # 3. Restore roles @@ -225,8 +221,8 @@ class Convocation(commands.Cog): restored_roles = [] if role_ids: for rid in role_ids: - role = ctx.guild.get_role(rid) - if role and role.position < ctx.guild.me.top_role.position: + role = interaction.guild.get_role(rid) + if role and role.position < interaction.guild.me.top_role.position: restored_roles.append(role) if restored_roles: @@ -235,27 +231,24 @@ class Convocation(commands.Cog): self.clear_backup(guild_id, membre.id) # 4. Notification - embed = discord.Embed( + embed = disnake.Embed( title="🔓 CONVOCATION TERMINÉE", - description=f"Votre convocation sur **{ctx.guild.name}** est terminée. Vos accès ont été restaurés.", - color=discord.Color.green() + description=f"Votre convocation sur **{interaction.guild.name}** est terminée. Vos accès ont été restaurés.", + color=disnake.Color.green() ) try: await membre.send(embed=embed) except: pass - await ctx.send(f"✅ Convocation levée pour {membre.mention}. Access restaurés.", ephemeral=True) + await interaction.followup.send(f"✅ Convocation levée pour {membre.mention}. Access restaurés.", ephemeral=True) - @convocation_group.command(name="config", description="Configure le système de convocation") - @app_commands.describe( - role="Le rôle de convocation (Muet/Convocation)", - channel="Le salon de logs", - attente="Salon (ou catégorie) d'attente à laisser visible", - admins="Liste d'IDs d'admins à notifier (séparés par des espaces)" - ) - @commands.has_permissions(administrator=True) - async def config(self, ctx, role: discord.Role, channel: discord.TextChannel = None, attente: discord.abc.GuildChannel = None, admins: str = None): - guild_id = ctx.guild.id + @convocation_group.sub_command(name="config", description="Configure le système de convocation") + async def config(self, interaction: disnake.ApplicationCommandInteraction, + role: disnake.Role = commands.Param(description="Le rôle de convocation (Muet/Convocation)"), + channel: disnake.TextChannel = commands.Param(None, description="Le salon de logs"), + attente: disnake.abc.GuildChannel = commands.Param(None, description="Salon (ou catégorie) d'attente à laisser visible"), + admins: str = commands.Param(None, description="Liste d'IDs d'admins à notifier (séparés par des espaces)")): + guild_id = interaction.guild.id settings = self.get_guild_settings(guild_id) settings["role_id"] = role.id @@ -273,13 +266,13 @@ class Convocation(commands.Cog): self.save_guild_settings(guild_id, settings) - embed = discord.Embed(title="⚙️ Configuration Convocation Mise à jour", color=discord.Color.blue()) + embed = disnake.Embed(title="⚙️ Configuration Convocation Mise à jour", color=disnake.Color.blue()) embed.add_field(name="Rôle", value=role.mention) embed.add_field(name="Salon Logs", value=channel.mention if channel else "Non défini") embed.add_field(name="Salon/Cat d'attente", value=attente.mention if attente else "Non défini") embed.add_field(name="Admins notifiés", value=str(len(settings.get("admin_ids", [])))) - await ctx.send(embed=embed, ephemeral=True) + await interaction.response.send_message(embed=embed, ephemeral=True) async def setup(bot): await bot.add_cog(Convocation(bot)) diff --git a/commandes/goodbye.py b/commandes/goodbye.py index 2453c32..9e4497b 100644 --- a/commandes/goodbye.py +++ b/commandes/goodbye.py @@ -1,6 +1,5 @@ -import discord -from discord.ext import commands -from discord import app_commands +import disnake +from disnake.ext import commands import sqlite3 import aiohttp import io @@ -148,14 +147,14 @@ class Goodbye(commands.Cog): # Utiliser le message personnalisé ou un par défaut description = custom_message if custom_message else "Nous espérons vous revoir bientôt " - embed = discord.Embed(title=f"{member} viens de nous quitter.", + embed = disnake.Embed(title=f"{member} viens de nous quitter.", description=description, - color=discord.Color.dark_magenta()) + color=disnake.Color.dark_magenta()) embed.set_footer(text=f"Avait rejoint {joined_message}") embed.set_image(url="attachment://goodbye.png") - await channel.send(embed=embed, file=discord.File(img, filename="goodbye.png")) + await channel.send(embed=embed, file=disnake.File(img, filename="goodbye.png")) - async def save_banner_attachment(self, attachment: discord.Attachment, guild_id: int) -> str: + async def save_banner_attachment(self, attachment: disnake.Attachment, guild_id: int) -> str: """Sauvegarde l'attachment et retourne le chemin du fichier""" base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "banners")) os.makedirs(base_dir, exist_ok=True) @@ -187,9 +186,9 @@ class Goodbye(commands.Cog): return os.path.basename(banner_path) - @app_commands.command(name="setgoodbye", description="Définit le salon d'aurevoir et les paramètres personnalisés") - @app_commands.checks.has_permissions(administrator=True) - async def set_goodbye(self, interaction: discord.Interaction, channel: discord.TextChannel, server_name: str = None, message: str = None, banner_file: discord.Attachment = None): + @commands.slash_command(name="setgoodbye", description="Définit le salon d'aurevoir et les paramètres personnalisés") + @commands.has_permissions(administrator=True) + async def set_goodbye(self, interaction: disnake.ApplicationCommandInteraction, channel: disnake.TextChannel, server_name: str = None, message: str = None, banner_file: disnake.Attachment = None): banner_path = None # Si un fichier est uploadé, le sauvegarder @@ -208,7 +207,7 @@ class Goodbye(commands.Cog): goodbye_server_name=COALESCE(excluded.goodbye_server_name, goodbye_server_name), goodbye_message=COALESCE(excluded.goodbye_message, goodbye_message), goodbye_banner_background=COALESCE(excluded.goodbye_banner_background, goodbye_banner_background)''', - (interaction.guild_id, channel.id, server_name, message, banner_path)) + (interaction.guild_id, channel.id, server_name, message, banner_path)) conn.commit() conn.close() diff --git a/commandes/moderation.py b/commandes/moderation.py index 379b05f..3f1174e 100644 --- a/commandes/moderation.py +++ b/commandes/moderation.py @@ -1,18 +1,17 @@ -import discord -from discord.ext import commands -from discord import app_commands +import disnake +from disnake.ext import commands import sqlite3 import os from datetime import datetime, timedelta import re -class ModReasonModal(discord.ui.Modal): +class ModReasonModal(disnake.ui.Modal): def __init__(self, parent_view, action_type): super().__init__(title=f"Raison pour {action_type}") self.parent_view = parent_view self.action_type = action_type - self.reason = discord.ui.TextInput( + self.reason = disnake.ui.TextInput( label="Raison", placeholder="Entrez la raison ici...", required=True, @@ -21,7 +20,7 @@ class ModReasonModal(discord.ui.Modal): self.add_item(self.reason) if action_type == "TIMEOUT": - self.duration = discord.ui.TextInput( + self.duration = disnake.ui.TextInput( label="Durée (ex: 1h, 30m, 1d)", placeholder="1h", required=True, @@ -29,11 +28,11 @@ class ModReasonModal(discord.ui.Modal): ) self.add_item(self.duration) - async def on_submit(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.Interaction): reason = self.reason.value member = self.parent_view.member - cog = interaction.client.get_cog("Moderation") + cog = interaction.bot.get_cog("Moderation") if not cog: return await interaction.response.send_message("❌ Erreur interne.", ephemeral=True) @@ -48,24 +47,24 @@ class ModReasonModal(discord.ui.Modal): await self.parent_view.update_panel(interaction) -class ModPanelView(discord.ui.View): +class ModPanelView(disnake.ui.View): def __init__(self, member, moderator, db_path): super().__init__(timeout=120) self.member = member self.moderator = moderator self.db_path = db_path - async def update_panel(self, interaction: discord.Interaction): + async def update_panel(self, interaction: disnake.Interaction): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute('SELECT COUNT(*) FROM sanctions WHERE guild_id = ? AND user_id = ? AND type = "WARN" AND status = "ACTIVE"', (interaction.guild_id, self.member.id)) warn_count = cursor.fetchone()[0] conn.close() - embed = discord.Embed( + embed = disnake.Embed( title=f"🛡️ Panel de Modération - {self.member.name}", description=f"Gestion de l'utilisateur {self.member.mention}", - color=discord.Color.blue(), + color=disnake.Color.blue(), timestamp=datetime.now() ) embed.set_thumbnail(url=self.member.display_avatar.url) @@ -78,38 +77,38 @@ class ModPanelView(discord.ui.View): else: await interaction.edit_original_response(embed=embed, view=self) - @discord.ui.button(label="Avertir", style=discord.ButtonStyle.secondary, emoji="⚠️") - async def warn_btn(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="Avertir", style=disnake.ButtonStyle.secondary, emoji="⚠️") + async def warn_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): await interaction.response.send_modal(ModReasonModal(self, "WARN")) - @discord.ui.button(label="Timeout", style=discord.ButtonStyle.secondary, emoji="⏳") - async def timeout_btn(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="Timeout", style=disnake.ButtonStyle.secondary, emoji="⏳") + async def timeout_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): await interaction.response.send_modal(ModReasonModal(self, "TIMEOUT")) - @discord.ui.button(label="Expulser", style=discord.ButtonStyle.secondary, emoji="👢") - async def kick_btn(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="Expulser", style=disnake.ButtonStyle.secondary, emoji="👢") + async def kick_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): await interaction.response.send_modal(ModReasonModal(self, "KICK")) - @discord.ui.button(label="Bannir", style=discord.ButtonStyle.danger, emoji="🔨") - async def ban_btn(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="Bannir", style=disnake.ButtonStyle.danger, emoji="🔨") + async def ban_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): await interaction.response.send_modal(ModReasonModal(self, "BAN")) - @discord.ui.button(label="Historique", style=discord.ButtonStyle.primary, emoji="📜") - async def history_btn(self, interaction: discord.Interaction, button: discord.ui.Button): - cog = interaction.client.get_cog("Moderation") + @disnake.ui.button(label="Historique", style=disnake.ButtonStyle.primary, emoji="📜") + async def history_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): + cog = interaction.bot.get_cog("Moderation") await cog.history(interaction, self.member) - @discord.ui.button(label="Clear Warns", style=discord.ButtonStyle.danger, emoji="🧹") - async def clear_btn(self, interaction: discord.Interaction, button: discord.ui.Button): - cog = interaction.client.get_cog("Moderation") + @disnake.ui.button(label="Clear Warns", style=disnake.ButtonStyle.danger, emoji="🧹") + async def clear_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): + cog = interaction.bot.get_cog("Moderation") await cog.clearwarns(interaction, self.member, reason="Nettoyage via Panel") await self.update_panel(interaction) -class ModConfigModal(discord.ui.Modal): +class ModConfigModal(disnake.ui.Modal): def __init__(self, field_name, current_value): super().__init__(title=f"Modifier {field_name}") self.field_name = field_name - self.input = discord.ui.TextInput( + self.input = disnake.ui.TextInput( label=field_name, default=str(current_value), placeholder="Entrez une valeur numérique...", @@ -117,9 +116,9 @@ class ModConfigModal(discord.ui.Modal): ) self.add_item(self.input) - async def on_submit(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.Interaction): value = self.input.value - cog = interaction.client.get_cog("Moderation") + cog = interaction.bot.get_cog("Moderation") mapping = { "Limite Timeout": "warn_limit_timeout", @@ -140,21 +139,21 @@ class ModConfigModal(discord.ui.Modal): await interaction.response.send_message(f"✅ {self.field_name} mis à jour sur **{value}**.", ephemeral=True) -class ModConfigView(discord.ui.View): +class ModConfigView(disnake.ui.View): def __init__(self, guild_id, db_path): super().__init__(timeout=60) self.guild_id = guild_id self.db_path = db_path - @discord.ui.button(label="Log Channel", style=discord.ButtonStyle.primary, emoji="📁") - async def log_btn(self, interaction: discord.Interaction, button: discord.ui.Button): - view = discord.ui.View() - select = discord.ui.ChannelSelect( + @disnake.ui.button(label="Log Channel", style=disnake.ButtonStyle.primary, emoji="📁") + async def log_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): + view = disnake.ui.View() + select = disnake.ui.ChannelSelect( placeholder="Sélectionnez le salon de logs...", - channel_types=[discord.ChannelType.text] + channel_types=[disnake.ChannelType.text] ) - async def select_callback(interaction: discord.Interaction): + async def select_callback(interaction: disnake.Interaction): channel = select.values[0] conn = sqlite3.connect(self.db_path) cursor = conn.cursor() @@ -166,29 +165,29 @@ class ModConfigView(discord.ui.View): view.add_item(select) await interaction.response.send_message("Choisissez le salon de logs :", view=view, ephemeral=True) - @discord.ui.button(label="Paliers Warns", style=discord.ButtonStyle.secondary, emoji="⚖️") - async def limits_btn(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="Paliers Warns", style=disnake.ButtonStyle.secondary, emoji="⚖️") + async def limits_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute("SELECT warn_limit_timeout, warn_limit_kick, warn_limit_ban FROM mod_config WHERE guild_id = ?", (self.guild_id,)) res = cursor.fetchone(); conn.close() - view = discord.ui.View() + view = disnake.ui.View() - btn_t = discord.ui.Button(label=f"Timeout ({res[0]})", style=discord.ButtonStyle.secondary) + btn_t = disnake.ui.Button(label=f"Timeout ({res[0]})", style=disnake.ButtonStyle.secondary) btn_t.callback = lambda i: i.response.send_modal(ModConfigModal("Limite Timeout", res[0])) - btn_k = discord.ui.Button(label=f"Kick ({res[1]})", style=discord.ButtonStyle.secondary) + btn_k = disnake.ui.Button(label=f"Kick ({res[1]})", style=disnake.ButtonStyle.secondary) btn_k.callback = lambda i: i.response.send_modal(ModConfigModal("Limite Kick", res[1])) - btn_b = discord.ui.Button(label=f"Ban ({res[2]})", style=discord.ButtonStyle.secondary) + btn_b = disnake.ui.Button(label=f"Ban ({res[2]})", style=disnake.ButtonStyle.secondary) btn_b.callback = lambda i: i.response.send_modal(ModConfigModal("Limite Ban", res[2])) view.add_item(btn_t); view.add_item(btn_k); view.add_item(btn_b) await interaction.response.send_message("Quelle limite modifier ?", view=view, ephemeral=True) - @discord.ui.button(label="Durée Timeout", style=discord.ButtonStyle.secondary, emoji="⏱️") - async def duration_btn(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="Durée Timeout", style=disnake.ButtonStyle.secondary, emoji="⏱️") + async def duration_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute("SELECT timeout_duration FROM mod_config WHERE guild_id = ?", (self.guild_id,)) @@ -225,38 +224,38 @@ class Moderation(commands.Cog): log_channel = guild.get_channel(result[0]) if log_channel: await log_channel.send(embed=embed) - @app_commands.command(name="modpanel", description="Ouvre le panel de modération pour un membre") - @app_commands.checks.has_permissions(moderate_members=True) - async def modpanel(self, interaction: discord.Interaction, member: discord.Member): + @commands.slash_command(name="modpanel", description="Ouvre le panel de modération pour un membre") + @commands.has_permissions(moderate_members=True) + async def modpanel(self, interaction: disnake.ApplicationCommandInteraction, member: disnake.Member): if member.top_role >= interaction.user.top_role and interaction.user.id != interaction.guild.owner_id: return await interaction.response.send_message("❌ Impossible de gérer ce membre.", ephemeral=True) conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute('SELECT COUNT(*) FROM sanctions WHERE guild_id = ? AND user_id = ? AND type = "WARN" AND status = "ACTIVE"', (interaction.guild_id, member.id)) warn_count = cursor.fetchone()[0]; conn.close() - embed = discord.Embed(title=f"🛡️ Panel de Modération - {member.name}", description=f"Gestion de {member.mention}", color=discord.Color.blue(), timestamp=datetime.now()) + embed = disnake.Embed(title=f"🛡️ Panel de Modération - {member.name}", description=f"Gestion de {member.mention}", color=disnake.Color.blue(), timestamp=datetime.now()) embed.set_thumbnail(url=member.display_avatar.url) embed.add_field(name="👤 Utilisateur", value=f"ID: `{member.id}`", inline=True) embed.add_field(name="⚠️ Avertissements", value=f"Actifs: **{warn_count}**", inline=True) view = ModPanelView(member, interaction.user, self.db_path) await interaction.response.send_message(embed=embed, view=view, ephemeral=True) - @app_commands.command(name="modconfig", description="Configuration de la modération") - @app_commands.checks.has_permissions(administrator=True) - async def modconfig(self, interaction: discord.Interaction): + @commands.slash_command(name="modconfig", description="Configuration de la modération") + @commands.has_permissions(administrator=True) + async def modconfig(self, interaction: disnake.ApplicationCommandInteraction): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute('INSERT OR IGNORE INTO mod_config (guild_id) VALUES (?)', (interaction.guild_id,)) cursor.execute('SELECT log_channel_id, warn_limit_timeout, warn_limit_kick, warn_limit_ban, timeout_duration FROM mod_config WHERE guild_id = ?', (interaction.guild_id,)) res = cursor.fetchone(); conn.commit(); conn.close() - embed = discord.Embed(title="⚙️ Configuration Modération", description="Modifiez les paramètres ci-dessous.", color=discord.Color.blue()) + embed = disnake.Embed(title="⚙️ Configuration Modération", description="Modifiez les paramètres ci-dessous.", color=disnake.Color.blue()) embed.add_field(name="📁 Logs", value=f"<#{res[0]}>" if res[0] else "Non défini", inline=False) embed.add_field(name="⚖️ Paliers", value=f"Timeout: **{res[1]}**, Kick: **{res[2]}**, Ban: **{res[3]}**", inline=False) embed.add_field(name="⏱️ Durée Timeout Auto", value=f"**{res[4]}s**", inline=False) view = ModConfigView(interaction.guild_id, self.db_path) await interaction.response.send_message(embed=embed, view=view, ephemeral=True) - async def warn(self, interaction: discord.Interaction, member: discord.Member, reason: str): + async def warn(self, interaction: disnake.Interaction, member: disnake.Member, reason: str): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute('INSERT INTO sanctions (guild_id, user_id, moderator_id, type, reason) VALUES (?, ?, ?, ?, ?)', (interaction.guild_id, member.id, interaction.user.id, 'WARN', reason)) @@ -266,26 +265,26 @@ class Moderation(commands.Cog): config = cursor.fetchone(); conn.commit(); conn.close() if not interaction.response.is_done(): await interaction.response.send_message(f"✅ {member.mention} averti ({warn_count}).", ephemeral=True) else: await interaction.followup.send(f"✅ {member.mention} averti ({warn_count}).", ephemeral=True) - await self.send_mod_log(interaction.guild, discord.Embed(title="⚠️ Avertissement", description=f"Membre: {member.mention}\nRaison: {reason}\nTotal: {warn_count}", color=discord.Color.orange())) + await self.send_mod_log(interaction.guild, disnake.Embed(title="⚠️ Avertissement", description=f"Membre: {member.mention}\nRaison: {reason}\nTotal: {warn_count}", color=disnake.Color.orange())) try: await member.send(f"⚠️ Avertissement sur **{interaction.guild.name}**\nRaison: {reason}") except: pass if config: l_t, l_k, l_b, dur = config if warn_count >= l_b: await member.ban(reason=f"Auto: {warn_count} warns") elif warn_count >= l_k: await member.kick(reason=f"Auto: {warn_count} warns") - elif warn_count >= l_t: await member.timeout(timedelta(seconds=dur), reason=f"Auto: {warn_count} warns") + elif warn_count >= l_t: await member.timeout(duration=timedelta(seconds=dur), reason=f"Auto: {warn_count} warns") - async def timeout(self, interaction: discord.Interaction, member: discord.Member, duration_str: str, reason: str): + async def timeout(self, interaction: disnake.Interaction, member: disnake.Member, duration_str: str, reason: str): secs = self.parse_duration(duration_str) if secs <= 0: return await interaction.response.send_message("❌ Durée invalide.", ephemeral=True) - await member.timeout(timedelta(seconds=secs), reason=reason) + await member.timeout(duration=timedelta(seconds=secs), reason=reason) conn = sqlite3.connect(self.db_path); cursor = conn.cursor() cursor.execute('INSERT INTO sanctions (guild_id, user_id, moderator_id, type, reason, duration) VALUES (?, ?, ?, ?, ?, ?)', (interaction.guild_id, member.id, interaction.user.id, 'TIMEOUT', reason, secs)) conn.commit(); conn.close() if not interaction.response.is_done(): await interaction.response.send_message(f"✅ {member.mention} mis en sourdine.", ephemeral=True) else: await interaction.followup.send(f"✅ {member.mention} mis en sourdine.", ephemeral=True) - async def kick(self, interaction: discord.Interaction, member: discord.Member, reason: str): + async def kick(self, interaction: disnake.Interaction, member: disnake.Member, reason: str): await member.kick(reason=reason) conn = sqlite3.connect(self.db_path); cursor = conn.cursor() cursor.execute('INSERT INTO sanctions (guild_id, user_id, moderator_id, type, reason) VALUES (?, ?, ?, ?, ?)', (interaction.guild_id, member.id, interaction.user.id, 'KICK', reason)) @@ -293,7 +292,7 @@ class Moderation(commands.Cog): if not interaction.response.is_done(): await interaction.response.send_message(f"✅ {member.name} expulsé.", ephemeral=True) else: await interaction.followup.send(f"✅ {member.name} expulsé.", ephemeral=True) - async def ban(self, interaction: discord.Interaction, user: discord.User, reason: str): + async def ban(self, interaction: disnake.Interaction, user: disnake.User, reason: str): await interaction.guild.ban(user, reason=reason) conn = sqlite3.connect(self.db_path); cursor = conn.cursor() cursor.execute('INSERT INTO sanctions (guild_id, user_id, moderator_id, type, reason) VALUES (?, ?, ?, ?, ?)', (interaction.guild_id, user.id, interaction.user.id, 'BAN', reason)) @@ -301,20 +300,20 @@ class Moderation(commands.Cog): if not interaction.response.is_done(): await interaction.response.send_message(f"✅ {user.name} banni.", ephemeral=True) else: await interaction.followup.send(f"✅ {user.name} banni.", ephemeral=True) - async def history(self, interaction: discord.Interaction, user: discord.User): + async def history(self, interaction: disnake.Interaction, user: disnake.User): conn = sqlite3.connect(self.db_path); cursor = conn.cursor() cursor.execute('SELECT id, type, reason, timestamp FROM sanctions WHERE guild_id = ? AND user_id = ? ORDER BY timestamp DESC', (interaction.guild_id, user.id)) results = cursor.fetchall(); conn.close() if not results: if not interaction.response.is_done(): return await interaction.response.send_message("ℹ️ Aucun historique.", ephemeral=True) else: return await interaction.followup.send("ℹ️ Aucun historique.", ephemeral=True) - embed = discord.Embed(title=f"📜 Historique de {user.name}", color=discord.Color.blue()) + embed = disnake.Embed(title=f"📜 Historique de {user.name}", color=disnake.Color.blue()) for s_id, s_type, reason, ts in results[:10]: embed.add_field(name=f"#{s_id} - {s_type}", value=f"**Date:** {ts}\n**Raison:** {reason}", inline=False) if not interaction.response.is_done(): await interaction.response.send_message(embed=embed, ephemeral=True) else: await interaction.followup.send(embed=embed, ephemeral=True) - async def clearwarns(self, interaction: discord.Interaction, member: discord.Member, reason: str): + async def clearwarns(self, interaction: disnake.Interaction, member: disnake.Member, reason: str): conn = sqlite3.connect(self.db_path); cursor = conn.cursor() cursor.execute('UPDATE sanctions SET status = "REVOKED" WHERE guild_id = ? AND user_id = ? AND type = "WARN"', (interaction.guild_id, member.id)) conn.commit(); conn.close() diff --git a/commandes/modules_security/antiraid.py b/commandes/modules_security/antiraid.py index a56deae..6ad47ad 100644 --- a/commandes/modules_security/antiraid.py +++ b/commandes/modules_security/antiraid.py @@ -1,5 +1,5 @@ -import discord -from discord.ext import commands +import disnake +from disnake.ext import commands import json import os from datetime import datetime @@ -53,7 +53,7 @@ class AntiRaid(commands.Cog): if log_channel_id: channel = guild.get_channel(int(log_channel_id)) if channel: - embed = discord.Embed(title="🚨 ALERTE RAID", description=f"Vague de {len(self.join_cache[guild.id])} membres !", color=0xFF0000) + embed = disnake.Embed(title="🚨 ALERTE RAID", description=f"Vague de {len(self.join_cache[guild.id])} membres !", color=0xFF0000) await channel.send(embed=embed) async def setup(bot): diff --git a/commandes/modules_security/antispam.py b/commandes/modules_security/antispam.py index 9ab7f81..fef7ba2 100644 --- a/commandes/modules_security/antispam.py +++ b/commandes/modules_security/antispam.py @@ -1,5 +1,5 @@ -import discord -from discord.ext import commands +import disnake +from disnake.ext import commands import json import os from collections import deque @@ -28,7 +28,7 @@ class AntiSpam(commands.Cog): def _get_cached_settings(self, guild_id): """Récupère les paramètres avec cache (TTL de 5 secondes)""" - now = discord.utils.utcnow().timestamp() + now = disnake.utils.utcnow().timestamp() cached = self._settings_cache.get(guild_id) if cached: @@ -52,7 +52,7 @@ class AntiSpam(commands.Cog): def _get_cached_whitelist(self, guild_id): """Récupère la whitelist avec cache (TTL de 5 secondes)""" - now = discord.utils.utcnow().timestamp() + now = disnake.utils.utcnow().timestamp() cached = self._whitelist_cache.get(guild_id) if cached: @@ -117,7 +117,7 @@ class AntiSpam(commands.Cog): # --- TEST ANTI-SPAM (deque pour O(1)) --- if settings.get("anti_spam") is True: - now = discord.utils.utcnow().timestamp() + now = disnake.utils.utcnow().timestamp() # Initialiser ou récupérer le deque pour cet utilisateur if user_id not in self.message_counts: @@ -156,7 +156,7 @@ class AntiSpam(commands.Cog): self.message_counts[user_id] = deque(maxlen=10) if current_warns <= 3: - embed = discord.Embed( + embed = disnake.Embed( title="🛡️ Sécurité Kuby", description=f"{message.author.mention}, {reason}.\n**Avertissement : {current_warns}/3**", color=0xFFA500 @@ -169,10 +169,10 @@ class AntiSpam(commands.Cog): async def apply_mute(self, message, reason): try: - until = discord.utils.utcnow() + timedelta(minutes=5) + until = disnake.utils.utcnow() + timedelta(minutes=5) await message.author.timeout(until, reason=f"Kuby Security: {reason} (3 Warns)") - embed = discord.Embed( + embed = disnake.Embed( title="⛔ Exclusion Temporaire", description=f"**Membre :** {message.author.mention}\n**Durée :** 5 minutes\n**Raison :** {reason} répétitif.", color=0xFF0000 diff --git a/commandes/modules_security/logs_manager.py b/commandes/modules_security/logs_manager.py index b596b54..dc3238c 100644 --- a/commandes/modules_security/logs_manager.py +++ b/commandes/modules_security/logs_manager.py @@ -1,5 +1,5 @@ -import discord -from discord.ext import commands +import disnake +from disnake.ext import commands import json import os from datetime import datetime @@ -23,11 +23,11 @@ class LogsManager(commands.Cog): log_chan = get_log_channel(message.guild, settings) if log_chan: - embed = discord.Embed( + embed = disnake.Embed( title="🗑️ Message Supprimé", description=f"**Auteur :** {message.author.mention}\n**Salon :** {message.channel.mention}", color=0xFFA500, - timestamp=datetime.utcnow() + timestamp=disnake.utils.utcnow() ) embed.add_field(name="Contenu", value=message.content or "*(Image ou Embed)*") await log_chan.send(embed=embed) @@ -37,11 +37,11 @@ class LogsManager(commands.Cog): settings = dict(json.load(open(SETTINGS_FILE, "r"))).get(str(member.guild.id), {}) log_chan = get_log_channel(member.guild, settings) if log_chan: - embed = discord.Embed( + embed = disnake.Embed( title="📤 Départ d'un membre", description=f"{member.mention} ({member.id}) a quitté le serveur.", color=0xFF0000, - timestamp=datetime.utcnow() + timestamp=disnake.utils.utcnow() ) await log_chan.send(embed=embed) @@ -50,11 +50,11 @@ class LogsManager(commands.Cog): settings = dict(json.load(open(SETTINGS_FILE, "r"))).get(str(guild.id), {}) log_chan = get_log_channel(guild, settings) if log_chan: - embed = discord.Embed( + embed = disnake.Embed( title="🔨 Bannissement", description=f"**Utilisateur :** {user.mention} ({user.id}) a été banni.", color=0x000000, - timestamp=datetime.utcnow() + timestamp=disnake.utils.utcnow() ) await log_chan.send(embed=embed) diff --git a/commandes/modules_security/rules.py b/commandes/modules_security/rules.py index 6290dd9..6f5999a 100644 --- a/commandes/modules_security/rules.py +++ b/commandes/modules_security/rules.py @@ -1,6 +1,5 @@ -import discord -from discord import app_commands -from discord.ext import commands +import disnake +from disnake.ext import commands import json import os from typing import Optional @@ -39,9 +38,9 @@ def save_settings(guild_id: int, settings: dict) -> None: # --- MODALS DE CONFIGURATION DU RÈGLEMENT (BACK-END) --- -class RulesTitleModal(discord.ui.Modal, title="📜 Titre du Règlement"): +class RulesTitleModal(disnake.ui.Modal, title="📜 Titre du Règlement"): """Modal pour configurer le titre du règlement""" - title_input = discord.ui.TextInput( + title_input = disnake.ui.TextInput( label="Titre du règlement", placeholder="Ex: 📜 Règlement de [Nom du Serveur]", min_length=1, @@ -55,7 +54,7 @@ class RulesTitleModal(discord.ui.Modal, title="📜 Titre du Règlement"): self.settings = settings self.on_update_callback = on_update_callback - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): self.settings["rules_title"] = self.title_input.value save_settings(self.guild_id, self.settings) @@ -78,12 +77,12 @@ class RulesTitleModal(discord.ui.Modal, title="📜 Titre du Règlement"): return view -class RulesContentModal(discord.ui.Modal, title="📝 Contenu du Règlement"): +class RulesContentModal(disnake.ui.Modal, title="📝 Contenu du Règlement"): """Modal pour configurer le contenu du règlement""" - content_input = discord.ui.TextInput( + content_input = disnake.ui.TextInput( label="Contenu du règlement", placeholder="Entrez les règles de votre serveur...", - style=discord.TextStyle.paragraph, + style=disnake.TextStyle.paragraph, min_length=10, max_length=4000, required=True @@ -95,7 +94,7 @@ class RulesContentModal(discord.ui.Modal, title="📝 Contenu du Règlement"): self.settings = settings self.on_update_callback = on_update_callback - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): self.settings["rules_content"] = self.content_input.value save_settings(self.guild_id, self.settings) @@ -118,9 +117,9 @@ class RulesContentModal(discord.ui.Modal, title="📝 Contenu du Règlement"): return view -class RulesChannelModal(discord.ui.Modal, title="📌 Salon du Règlement"): +class RulesChannelModal(disnake.ui.Modal, title="📌 Salon du Règlement"): """Modal pour sélectionner le salon du règlement""" - channel_input = discord.ui.TextInput( + channel_input = disnake.ui.TextInput( label="ID du salon", placeholder="Entrez l'ID du salon où envoyer le règlement", min_length=15, @@ -134,7 +133,7 @@ class RulesChannelModal(discord.ui.Modal, title="📌 Salon du Règlement"): self.settings = settings self.on_update_callback = on_update_callback - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): if not self.channel_input.value.isdigit(): return await interaction.response.send_message( "❌ Erreur : ID invalide.", @@ -172,9 +171,9 @@ class RulesChannelModal(discord.ui.Modal, title="📌 Salon du Règlement"): return view -class RulesRoleModal(discord.ui.Modal, title="✅ Rôle d'Acceptation"): +class RulesRoleModal(disnake.ui.Modal, title="✅ Rôle d'Acceptation"): """Modal pour configurer le rôle attribué à l'acceptation""" - role_input = discord.ui.TextInput( + role_input = disnake.ui.TextInput( label="ID du rôle", placeholder="Entrez l'ID du rôle à attribuer", min_length=15, @@ -188,7 +187,7 @@ class RulesRoleModal(discord.ui.Modal, title="✅ Rôle d'Acceptation"): self.settings = settings self.on_update_callback = on_update_callback - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): if not self.role_input.value.isdigit(): return await interaction.response.send_message( "❌ Erreur : ID invalide.", @@ -226,9 +225,9 @@ class RulesRoleModal(discord.ui.Modal, title="✅ Rôle d'Acceptation"): return view -class RulesMessageTypeModal(discord.ui.Modal, title="📝 Type de Message"): +class RulesMessageTypeModal(disnake.ui.Modal, title="📝 Type de Message"): """Modal pour choisir le type de message du règlement""" - message_type = discord.ui.TextInput( + message_type = disnake.ui.TextInput( label="Type de message", placeholder="Tapez 'embed' pour embed ou 'simple' pour message simple", min_length=5, @@ -242,7 +241,7 @@ class RulesMessageTypeModal(discord.ui.Modal, title="📝 Type de Message"): self.settings = settings self.on_update_callback = on_update_callback - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): msg_type = self.message_type.value.lower().strip() if msg_type not in ["embed", "simple"]: @@ -274,11 +273,11 @@ class RulesMessageTypeModal(discord.ui.Modal, title="📝 Type de Message"): return view -class ToggleRulesButton(discord.ui.Button): +class ToggleRulesButton(disnake.ui.Button): """Bouton de bascule pour activer/désactiver le règlement""" def __init__(self, is_enabled: bool): - style = discord.ButtonStyle.green if is_enabled else discord.ButtonStyle.red + style = disnake.ButtonStyle.green if is_enabled else disnake.ButtonStyle.red status = "✅" if is_enabled else "❌" super().__init__( label=f"Règlement [{status}]", @@ -287,7 +286,7 @@ class ToggleRulesButton(discord.ui.Button): emoji="📜" ) - async def callback(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.Interaction): view = self.view settings = view.settings guild = interaction.guild @@ -316,7 +315,7 @@ class ToggleRulesButton(discord.ui.Button): await interaction.response.edit_message(embed=embed, view=new_view) -class RulesAcceptButton(discord.ui.View): +class RulesAcceptButton(disnake.ui.View): """Bouton persistant pour accepter le règlement""" def __init__(self, guild_id: int, accept_role_id: int): @@ -327,10 +326,10 @@ class RulesAcceptButton(discord.ui.View): @discord.ui.button( label="J'accepte le règlement", emoji="✅", - style=discord.ButtonStyle.green, + style=disnake.ButtonStyle.green, custom_id="rules_accept_button" ) - async def accept_rules(self, interaction: discord.Interaction, button: discord.ui.Button): + async def accept_rules(self, interaction: disnake.Interaction, button: disnake.ui.Button): """Gère le clic sur le bouton d'acceptation""" guild = interaction.guild @@ -368,7 +367,7 @@ class RulesAcceptButton(discord.ui.View): f"Le rôle **{role.name}** vous a été attribué.", ephemeral=True ) - except discord.Forbidden: + except disnake.Forbidden: await interaction.response.send_message( "❌ Je n'ai pas les permissions pour attribuer ce rôle.", ephemeral=True @@ -421,7 +420,7 @@ class RulesCog(commands.Cog): """Appelé quand le bot est prêt""" print("🎫 Module Règles chargé avec succès!") - async def send_rules_message(self, channel: discord.TextChannel, settings: dict) -> Optional[discord.Message]: + 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", "") @@ -457,7 +456,7 @@ class RulesCog(commands.Cog): else: # embed # Créer l'embed - embed = discord.Embed( + embed = disnake.Embed( title=title, description=content, color=0x2b2d31 diff --git a/commandes/no_link.py b/commandes/no_link.py index c708ec2..e3ad27b 100755 --- a/commandes/no_link.py +++ b/commandes/no_link.py @@ -1,7 +1,7 @@ # utils/no_link.py import re -import discord -from discord.ext import commands +import disnake +from disnake.ext import commands import json import os @@ -26,7 +26,7 @@ class NoLink(commands.Cog): self.bot = bot @commands.Cog.listener() - async def on_message(self, message: discord.Message) -> None: + async def on_message(self, message: disnake.Message) -> None: if message.author.bot or not message.guild: return @@ -41,7 +41,7 @@ class NoLink(commands.Cog): f"{message.author.mention} ⚠️ Les liens sont interdits sur ce serveur.", delete_after=5 ) - except discord.Forbidden: + except disnake.Forbidden: print(f"Impossible de supprimer le message de {message.author} (permissions manquantes).") except Exception as exc: print(f"Erreur lors de la suppression du message : {exc}") diff --git a/commandes/scan.py b/commandes/scan.py index bc74e57..db9c1d4 100755 --- a/commandes/scan.py +++ b/commandes/scan.py @@ -1,7 +1,6 @@ # scan.py -import discord -from discord import app_commands -from discord.ext import commands +import disnake +from disnake.ext import commands import hashlib import os import json @@ -59,8 +58,8 @@ class Scan(commands.Cog): def __init__(self, bot: commands.Bot): self.bot = bot - @app_commands.command(name="scan", description="Scanner un fichier pour détecter un malware.") - async def scan(self, interaction: discord.Interaction, file: discord.Attachment): + @commands.slash_command(name="scan", description="Scanner un fichier pour détecter un malware.") + async def scan(self, interaction: disnake.ApplicationCommandInteraction, file: disnake.Attachment): await interaction.response.defer() # Permet de prendre un peu de temps pour le scan # Téléchargement temporaire du fichier diff --git a/commandes/security.py b/commandes/security.py index 5a461a0..a839a93 100755 --- a/commandes/security.py +++ b/commandes/security.py @@ -1,6 +1,5 @@ -import discord -from discord import app_commands -from discord.ext import commands +import disnake +from disnake.ext import commands import json import os from typing import Optional @@ -70,9 +69,9 @@ def is_immune(guild, user) -> bool: # --- MODALS DE CONFIGURATION --- -class SpamThresholdModal(discord.ui.Modal, title="🛡️ Seuil Anti-Spam"): +class SpamThresholdModal(disnake.ui.Modal, title="🛡️ Seuil Anti-Spam"): """Modal pour configurer le seuil anti-spam""" - threshold_input = discord.ui.TextInput( + threshold_input = disnake.ui.TextInput( label="Messages max autorisés", placeholder="Entre 3 et 10 (défaut: 5)", min_length=1, @@ -86,7 +85,7 @@ class SpamThresholdModal(discord.ui.Modal, title="🛡️ Seuil Anti-Spam"): self.settings = settings self.parent_view = view - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): if not self.threshold_input.value.isdigit(): return await interaction.response.send_message( "❌ Erreur : Veuillez entrer un nombre entier.", @@ -111,9 +110,9 @@ class SpamThresholdModal(discord.ui.Modal, title="🛡️ Seuil Anti-Spam"): ephemeral=True ) -class RaidThresholdModal(discord.ui.Modal, title="⚔️ Seuil Anti-Raid"): +class RaidThresholdModal(disnake.ui.Modal, title="⚔️ Seuil Anti-Raid"): """Modal pour configurer le seuil anti-raid""" - threshold_input = discord.ui.TextInput( + threshold_input = disnake.ui.TextInput( label="Joins max en 10 secondes", placeholder="Entre 2 et 10 (défaut: 3)", min_length=1, @@ -127,7 +126,7 @@ class RaidThresholdModal(discord.ui.Modal, title="⚔️ Seuil Anti-Raid"): self.settings = settings self.parent_view = view - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): if not self.threshold_input.value.isdigit(): return await interaction.response.send_message( "❌ Erreur : Veuillez entrer un nombre entier.", @@ -154,9 +153,9 @@ class RaidThresholdModal(discord.ui.Modal, title="⚔️ Seuil Anti-Raid"): # --- MODALS DE CONFIGURATION WHITELIST --- -class WhitelistModal(discord.ui.Modal, title="👤 Ajouter à la Whitelist"): +class WhitelistModal(disnake.ui.Modal, title="👤 Ajouter à la Whitelist"): """Modal pour ajouter un utilisateur à la whitelist""" - user_input = discord.ui.TextInput( + user_input = disnake.ui.TextInput( label="ID de l'utilisateur", placeholder="Entrez l'ID Discord de l'utilisateur", min_length=15, @@ -169,7 +168,7 @@ class WhitelistModal(discord.ui.Modal, title="👤 Ajouter à la Whitelist"): self.guild_id = guild_id self.parent_view = view - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): if not self.user_input.value.isdigit(): return await interaction.response.send_message( "❌ Erreur : ID invalide.", @@ -213,10 +212,10 @@ class WhitelistModal(discord.ui.Modal, title="👤 Ajouter à la Whitelist"): # --- VUES DE L'INTERFACE --- -class SecurityView(discord.ui.View): +class SecurityView(disnake.ui.View): """Vue principale du panneau de sécurité""" - def __init__(self, interaction: discord.Interaction, settings: dict, category: Optional[str] = None, bot=None): + 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 @@ -270,18 +269,18 @@ class SecurityView(discord.ui.View): # Bouton de configuration du seuil threshold = self.settings.get("spam_threshold", 5) - btn_threshold = discord.ui.Button( + btn_threshold = disnake.ui.Button( label=f"🔢 Seuil: {threshold} msg/5s", - style=discord.ButtonStyle.blurple, + 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 = discord.ui.Button( + btn_back = disnake.ui.Button( label="Retour", - style=discord.ButtonStyle.gray, + style=disnake.ButtonStyle.gray, emoji="⬅️" ) btn_back.callback = self.back_to_main @@ -311,18 +310,18 @@ class SecurityView(discord.ui.View): # Bouton de configuration du seuil threshold = self.settings.get("raid_threshold", 3) - btn_threshold = discord.ui.Button( + btn_threshold = disnake.ui.Button( label=f"🔢 Seuil: {threshold} join/10s", - style=discord.ButtonStyle.blurple, + 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 = discord.ui.Button( + btn_back = disnake.ui.Button( label="Retour", - style=discord.ButtonStyle.gray, + style=disnake.ButtonStyle.gray, emoji="⬅️" ) btn_back.callback = self.back_to_main @@ -351,9 +350,9 @@ class SecurityView(discord.ui.View): self.add_item(btn_role) # Bouton retour - btn_back = discord.ui.Button( + btn_back = disnake.ui.Button( label="Retour", - style=discord.ButtonStyle.gray, + style=disnake.ButtonStyle.gray, emoji="⬅️" ) btn_back.callback = self.back_to_main @@ -376,23 +375,23 @@ class SecurityView(discord.ui.View): 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( + btn_channel = disnake.ui.Button( label=f"📌 Channel: {channel.name if channel else 'Invalide'}", - style=discord.ButtonStyle.green, + style=disnake.ButtonStyle.green, disabled=True ) else: - btn_channel = discord.ui.Button( + btn_channel = disnake.ui.Button( label="📌 Aucun channel configuré", - style=discord.ButtonStyle.red, + style=disnake.ButtonStyle.red, disabled=True ) self.add_item(btn_channel) # Bouton retour - btn_back = discord.ui.Button( + btn_back = disnake.ui.Button( label="Retour", - style=discord.ButtonStyle.gray, + style=disnake.ButtonStyle.gray, emoji="⬅️" ) btn_back.callback = self.back_to_main @@ -401,9 +400,9 @@ class SecurityView(discord.ui.View): def build_whitelist_view(self): """Construit la vue Whitelist""" # Affichage du nombre avec style - btn_count = discord.ui.Button( + btn_count = disnake.ui.Button( label=f"👥 Whitelist: {self.whitelist_count} utilisateur(s)", - style=discord.ButtonStyle.blurple, + style=disnake.ButtonStyle.blurple, disabled=True ) self.add_item(btn_count) @@ -413,17 +412,17 @@ class SecurityView(discord.ui.View): self.add_item(WhitelistSelect(self.guild, self)) else: # Message si whitelist vide - btn_empty = discord.ui.Button( + btn_empty = disnake.ui.Button( label="📭 Aucun utilisateur dans la whitelist", - style=discord.ButtonStyle.gray, + style=disnake.ButtonStyle.gray, disabled=True ) self.add_item(btn_empty) # Bouton ajouter - btn_add = discord.ui.Button( + btn_add = disnake.ui.Button( label="➕ Ajouter", - style=discord.ButtonStyle.green, + style=disnake.ButtonStyle.green, emoji="➕", custom_id="whitelist_add" ) @@ -431,9 +430,9 @@ class SecurityView(discord.ui.View): self.add_item(btn_add) # Bouton retour - btn_back = discord.ui.Button( + btn_back = disnake.ui.Button( label="Retour", - style=discord.ButtonStyle.gray, + style=disnake.ButtonStyle.gray, emoji="⬅️" ) btn_back.callback = self.back_to_main @@ -458,18 +457,18 @@ class SecurityView(discord.ui.View): # Bouton pour modifier le titre - btn_title = discord.ui.Button( + btn_title = disnake.ui.Button( label="Éditer le titre", - style=discord.ButtonStyle.blurple, + 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 = discord.ui.Button( + btn_content = disnake.ui.Button( label="Éditer le contenu", - style=discord.ButtonStyle.blurple, + style=disnake.ButtonStyle.blurple, emoji="📄" ) btn_content.callback = self.open_rules_content_modal @@ -479,15 +478,15 @@ class SecurityView(discord.ui.View): rules_channel_id = self.settings.get("rules_channel_id") if rules_channel_id: channel = self.guild.get_channel(int(rules_channel_id)) - btn_channel = discord.ui.Button( + btn_channel = disnake.ui.Button( label=f"Salon: {channel.name if channel else 'Invalide'}", - style=discord.ButtonStyle.green, + style=disnake.ButtonStyle.green, custom_id="rules_channel" ) else: - btn_channel = discord.ui.Button( + btn_channel = disnake.ui.Button( label="Aucun salon configuré", - style=discord.ButtonStyle.red, + style=disnake.ButtonStyle.red, custom_id="rules_channel" ) btn_channel.callback = self.open_rules_channel_modal @@ -497,15 +496,15 @@ class SecurityView(discord.ui.View): 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 = discord.ui.Button( + btn_role = disnake.ui.Button( label=f"Rôle: {role.name if role else 'Invalide'}", - style=discord.ButtonStyle.green, + style=disnake.ButtonStyle.green, custom_id="rules_role" ) else: - btn_role = discord.ui.Button( + btn_role = disnake.ui.Button( label="Aucun rôle configuré", - style=discord.ButtonStyle.red, + style=disnake.ButtonStyle.red, custom_id="rules_role" ) btn_role.callback = self.open_rules_role_modal @@ -514,9 +513,9 @@ class SecurityView(discord.ui.View): # 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 = discord.ui.Button( + btn_message_type = disnake.ui.Button( label=f"Type: {type_label}", - style=discord.ButtonStyle.blurple, + style=disnake.ButtonStyle.blurple, emoji="📝", custom_id="rules_message_type" ) @@ -524,84 +523,84 @@ class SecurityView(discord.ui.View): self.add_item(btn_message_type) # Bouton pour envoyer le règlement - btn_send = discord.ui.Button( + btn_send = disnake.ui.Button( label="Envoyer le règlement", - style=discord.ButtonStyle.green, + style=disnake.ButtonStyle.green, emoji="🚀" ) btn_send.callback = self.send_rules self.add_item(btn_send) # Bouton retour - btn_back = discord.ui.Button( + btn_back = disnake.ui.Button( label="Retour", - style=discord.ButtonStyle.gray, + style=disnake.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): + 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: discord.Interaction): + 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: discord.Interaction): + 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: discord.Interaction): + 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: discord.Interaction): + 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: discord.Interaction): + 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: discord.Interaction): + 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: discord.Interaction): + 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: discord.Interaction): + 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: discord.Interaction): + async def send_rules(self, interaction: disnake.Interaction): """Envoie le règlement dans le salon configuré""" from commandes.modules_security.rules import RulesCog @@ -670,16 +669,16 @@ class SecurityView(discord.ui.View): ephemeral=True ) - async def back_to_main(self, interaction: discord.Interaction): + 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: discord.Member = None) -> discord.Embed: + def create_main_embed(self, user: disnake.Member = None) -> disnake.Embed: """Crée l'embed du menu principal""" - embed = discord.Embed( + embed = disnake.Embed( title="🛡️ Centre de Sécurité Kuby", description="Gérez la protection de votre serveur", color=0x7289DA @@ -726,9 +725,9 @@ class SecurityView(discord.ui.View): return embed - def create_category_embed(self, title: str, description: str) -> discord.Embed: + def create_category_embed(self, title: str, description: str) -> disnake.Embed: """Crée un embed pour une catégorie""" - embed = discord.Embed( + embed = disnake.Embed( title=title, description=description, color=0x7289DA @@ -740,11 +739,11 @@ class SecurityView(discord.ui.View): return embed -class ToggleButton(discord.ui.Button): +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 = discord.ButtonStyle.green if is_enabled else discord.ButtonStyle.red + style = disnake.ButtonStyle.green if is_enabled else disnake.ButtonStyle.red status = "✅" if is_enabled else "❌" super().__init__( label=f"{label} [{status}]", @@ -753,7 +752,7 @@ class ToggleButton(discord.ui.Button): emoji=emoji ) - async def callback(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.Interaction): view: SecurityView = self.view key = interaction.data['custom_id'] @@ -771,42 +770,42 @@ class ToggleButton(discord.ui.Button): await interaction.response.edit_message(view=view) -class MainCategorySelect(discord.ui.Select): +class MainCategorySelect(disnake.ui.Select): """Menu de sélection des catégories""" def __init__(self): options = [ - discord.SelectOption( + disnake.SelectOption( label="Anti-Spam", emoji="🛡️", value="spam", description="Protection contre le spam et les liens" ), - discord.SelectOption( + disnake.SelectOption( label="Anti-Raid", emoji="⚔️", value="raid", description="Protection contre les raids" ), - discord.SelectOption( + disnake.SelectOption( label="Anti-Nuke", emoji="☢️", value="nuke", description="Protection contre la destruction du serveur" ), - discord.SelectOption( + disnake.SelectOption( label="Logs", emoji="📋", value="logs", description="Journalisation des événements" ), - discord.SelectOption( + disnake.SelectOption( label="Règlement", emoji="📜", value="rules", description="Créer et envoyer un règlement" ), - discord.SelectOption( + disnake.SelectOption( label="Whitelist", emoji="👥", value="whitelist", @@ -820,7 +819,7 @@ class MainCategorySelect(discord.ui.Select): max_values=1 ) - async def callback(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.Interaction): view: SecurityView = self.view view.category = self.values[0] view.build_view() @@ -871,7 +870,7 @@ class MainCategorySelect(discord.ui.Select): await interaction.response.edit_message(embed=embed, view=view) -class WhitelistSelect(discord.ui.Select): +class WhitelistSelect(disnake.ui.Select): """Menu dropdown pour voir et gérer les utilisateurs whitelistés""" def __init__(self, guild: discord.Guild, view: 'SecurityView'): @@ -890,7 +889,7 @@ class WhitelistSelect(discord.ui.Select): label = f"ID: {user_id} (non trouvé)" emoji = "❓" options.append( - discord.SelectOption( + disnake.SelectOption( label=label[:100], # Discord limite à 100 chars value=str(user_id), emoji=emoji, @@ -905,7 +904,7 @@ class WhitelistSelect(discord.ui.Select): max_values=1 ) - async def callback(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.Interaction): """Gère la sélection d'un utilisateur à retirer""" user_id = int(self.values[0]) @@ -955,7 +954,7 @@ class Security(commands.Cog): name="security", description="🛡️ Ouvrir le panneau de sécurité Kuby" ) - async def security(self, interaction: discord.Interaction): + async def security(self, interaction: disnake.Interaction): """ Commande principale pour ouvrir l'interface de sécurité. Accessible uniquement aux administrateurs et propriétaires whitelistés. @@ -990,7 +989,7 @@ class Security(commands.Cog): ) @security.error - async def security_error(self, interaction: discord.Interaction, error: app_commands.AppCommandError): + async def security_error(self, interaction: disnake.Interaction, error: app_commands.AppCommandError): """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 diff --git a/commandes/sendbotmessages.py b/commandes/sendbotmessages.py index 10b52e7..058c2e4 100755 --- a/commandes/sendbotmessages.py +++ b/commandes/sendbotmessages.py @@ -1,17 +1,14 @@ -import discord -from discord import app_commands -from discord.ext import commands +import disnake +from disnake.ext import commands class SentBotMessages(commands.Cog): def __init__(self, bot): self.bot = bot - @app_commands.command(name="sentbotmessages", description="Le bot envoie un message dans un salon choisi.") - @app_commands.describe( - message="Le message à envoyer", - salon="Le salon où envoyer le message" - ) - async def sentbotmessages(self, interaction: discord.Interaction, message: str, salon: discord.TextChannel): + @commands.slash_command(name="sentbotmessages", description="Le bot envoie un message dans un salon choisi.") + async def sentbotmessages(self, interaction: disnake.ApplicationCommandInteraction, + message: str = commands.Param(description="Le message à envoyer"), + salon: disnake.TextChannel = commands.Param(description="Le salon où envoyer le message")): # Vérifie si l'utilisateur est admin if not interaction.user.guild_permissions.administrator: await interaction.response.send_message("⛔ Tu n'as pas la permission d'utiliser cette commande.", ephemeral=True) diff --git a/commandes/snipe.py b/commandes/snipe.py index 00182c5..f47cacc 100755 --- a/commandes/snipe.py +++ b/commandes/snipe.py @@ -1,6 +1,5 @@ -import discord -from discord import app_commands -from discord.ext import commands +import disnake +from disnake.ext import commands from datetime import datetime from collections import deque @@ -34,9 +33,9 @@ class SnipeCommands(commands.Cog): self.deleted_messages[channel_id].appendleft(message_data) - @app_commands.command(name="snipe", description="Voir les derniers messages supprimés d'un salon") - @app_commands.describe(nombre="Nombre de messages à afficher (1-10, par défaut: 1)") - async def snipe(self, interaction: discord.Interaction, nombre: int = 1): + @commands.slash_command(name="snipe", description="Voir les derniers messages supprimés d'un salon") + async def snipe(self, interaction: disnake.ApplicationCommandInteraction, + nombre: int = commands.Param(1, description="Nombre de messages à afficher (1-10, par défaut: 1)")): """Retrieve deleted messages from the current channel""" channel_id = interaction.channel.id @@ -60,9 +59,9 @@ class SnipeCommands(commands.Cog): messages = list(self.deleted_messages[channel_id])[:nombre] # Create embed for displaying deleted messages - embed = discord.Embed( + embed = disnake.Embed( title=f"🗑️ Messages supprimés ({len(messages)} sur {len(self.deleted_messages[channel_id])} disponibles)", - color=discord.Color.red() + color=disnake.Color.red() ) for i, msg_data in enumerate(messages, 1): @@ -93,8 +92,8 @@ class SnipeCommands(commands.Cog): await interaction.response.send_message(embed=embed) - @app_commands.command(name="snipeclear", description="Effacer l'historique des messages supprimés pour ce salon") - async def snipeclear(self, interaction: discord.Interaction): + @commands.slash_command(name="snipeclear", description="Effacer l'historique des messages supprimés pour ce salon") + async def snipeclear(self, interaction: disnake.ApplicationCommandInteraction): """Clear the deleted messages history for the current channel""" channel_id = interaction.channel.id diff --git a/commandes/staff_leaderboard.py b/commandes/staff_leaderboard.py index 9ca4653..9d1007d 100644 --- a/commandes/staff_leaderboard.py +++ b/commandes/staff_leaderboard.py @@ -1,6 +1,5 @@ -import discord -from discord.ext import commands -from discord import app_commands +import disnake +from disnake.ext import commands from datetime import datetime from commandes.ticket.data.storage import get_storage @@ -11,8 +10,8 @@ class StaffLeaderboard(commands.Cog): def __init__(self, bot): self.bot = bot - @app_commands.command(name="staff-leaderboard", description="Affiche le classement du staff") - async def staff_leaderboard(self, interaction: discord.Interaction): + @commands.slash_command(name="staff-leaderboard", description="Affiche le classement du staff") + async def staff_leaderboard(self, interaction: disnake.ApplicationCommandInteraction): """Commande pour afficher le classement du staff""" await interaction.response.defer(ephemeral=False) @@ -31,10 +30,10 @@ class StaffLeaderboard(commands.Cog): staff_list.sort(key=lambda x: x['average_rating'], reverse=True) # Create embed - embed = discord.Embed( + embed = disnake.Embed( title="🏆 Classement du Staff", description="Classement des meilleurs membres du staff", - color=discord.Color.gold(), + color=disnake.Color.gold(), timestamp=datetime.now() ) diff --git a/commandes/staff_ratings.py b/commandes/staff_ratings.py index 3e7a6a9..a777da0 100644 --- a/commandes/staff_ratings.py +++ b/commandes/staff_ratings.py @@ -1,6 +1,5 @@ -import discord -from discord.ext import commands -from discord import app_commands +import disnake +from disnake.ext import commands import matplotlib.pyplot as plt import plotly.graph_objects as go import plotly.express as px @@ -19,9 +18,9 @@ class StaffRatings(commands.Cog): def __init__(self, bot): self.bot = bot - @app_commands.command(name="staff-ratings", description="Affiche les évaluations du staff") - @app_commands.describe(team="Filtrer par équipe (optionnel)") - async def staff_ratings(self, interaction: discord.Interaction, team: str = None): + @commands.slash_command(name="staff-ratings", description="Affiche les évaluations du staff") + async def staff_ratings(self, interaction: disnake.ApplicationCommandInteraction, + team: str = commands.Param(None, description="Filtrer par équipe (optionnel)")): """Commande pour afficher les évaluations du staff""" await interaction.response.defer(ephemeral=False) @@ -46,10 +45,10 @@ class StaffRatings(commands.Cog): chart_buffer = self.create_staff_rating_chart_matplotlib(staff_list) # Create embed - embed = discord.Embed( + embed = disnake.Embed( title="📊 Évaluations du Staff", description="Performance globale du staff", - color=discord.Color.blue(), + color=disnake.Color.blue(), timestamp=datetime.now() ) @@ -66,7 +65,7 @@ class StaffRatings(commands.Cog): ) # Send chart - file = discord.File(chart_buffer, filename="staff_ratings.png") + file = disnake.File(chart_buffer, filename="staff_ratings.png") await interaction.followup.send(embed=embed, file=file) except Exception as e: diff --git a/commandes/ticket/__init__.py b/commandes/ticket/__init__.py index 209b100..1b2441b 100644 --- a/commandes/ticket/__init__.py +++ b/commandes/ticket/__init__.py @@ -20,8 +20,8 @@ from collections import defaultdict # Add parent directory to path for imports sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -import discord -from discord.ext import commands, tasks +import disnake +from disnake.ext import commands, tasks import traceback from PIL import Image, ImageDraw, ImageFont @@ -51,7 +51,7 @@ class FeedbackReminderTask: pass -class TicketPanelView(discord.ui.View): +class TicketPanelView(disnake.ui.View): """Panel principal avec toutes les options intégrées""" def __init__(self, bot, config: GuildTicketConfig, storage=None): @@ -59,7 +59,7 @@ class TicketPanelView(discord.ui.View): self.bot = bot self.config = config self.storage = storage or get_storage() - self.PURPLE = discord.Color.blurple() + self.PURPLE = disnake.Color.blurple() # Boutons du panel self._build_panel() @@ -69,8 +69,8 @@ class TicketPanelView(discord.ui.View): self.clear_items() # Créer ticket - create_btn = discord.ui.Button( - style=discord.ButtonStyle.blurple, + create_btn = disnake.ui.Button( + style=disnake.ButtonStyle.blurple, label="🎫 Créer un ticket", custom_id="ticket_create" ) @@ -78,8 +78,8 @@ class TicketPanelView(discord.ui.View): self.add_item(create_btn) # Mes tickets (si l'utilisateur a des tickets) - my_tickets_btn = discord.ui.Button( - style=discord.ButtonStyle.secondary, + my_tickets_btn = disnake.ui.Button( + style=disnake.ButtonStyle.secondary, label="📂 Mes tickets", custom_id="ticket_my_tickets" ) @@ -87,8 +87,8 @@ class TicketPanelView(discord.ui.View): self.add_item(my_tickets_btn) # Analytics (staff only) - analytics_btn = discord.ui.Button( - style=discord.ButtonStyle.primary, + analytics_btn = disnake.ui.Button( + style=disnake.ButtonStyle.primary, label="📊 Analytics", custom_id="ticket_analytics" ) @@ -96,8 +96,8 @@ class TicketPanelView(discord.ui.View): self.add_item(analytics_btn) # Staff Ratings (staff only) - staff_ratings_btn = discord.ui.Button( - style=discord.ButtonStyle.success, + staff_ratings_btn = disnake.ui.Button( + style=disnake.ButtonStyle.success, label="⭐ Notation du Staff", custom_id="ticket_staff_ratings" ) @@ -105,8 +105,8 @@ class TicketPanelView(discord.ui.View): self.add_item(staff_ratings_btn) # Configuration (admin only) - config_btn = discord.ui.Button( - style=discord.ButtonStyle.secondary, + config_btn = disnake.ui.Button( + style=disnake.ButtonStyle.secondary, label="⚙️ Configuration", custom_id="ticket_config" ) @@ -115,7 +115,7 @@ class TicketPanelView(discord.ui.View): - async def _create_callback(self, interaction: discord.Interaction): + async def _create_callback(self, interaction: disnake.Interaction): """Affiche les catégories pour créer un ticket""" # Reload config to ensure we have latest permissions/categories self.config = self.storage.load_config(interaction.guild_id) @@ -135,10 +135,10 @@ class TicketPanelView(discord.ui.View): # Créer view avec les catégories view = CategorySelectionView(self.bot, self.config) - embed = discord.Embed( + embed = disnake.Embed( title="Créer un Ticket", description="Sélectionnez une catégorie ci-dessous:", - color=discord.Color.blurple() + color=disnake.Color.blurple() ) for cat in self.config.categories[:5]: @@ -150,7 +150,7 @@ class TicketPanelView(discord.ui.View): await interaction.response.send_message(embed=embed, view=view, ephemeral=True) - async def _my_tickets_callback(self, interaction: discord.Interaction): + async def _my_tickets_callback(self, interaction: disnake.Interaction): """Affiche les tickets de l'utilisateur""" # Reload config self.config = self.storage.load_config(interaction.guild_id) @@ -166,10 +166,10 @@ class TicketPanelView(discord.ui.View): await interaction.response.send_message("Vous n'avez aucun ticket ouvert.", ephemeral=True) return - embed = discord.Embed( + embed = disnake.Embed( title="Vos Tickets Ouverts", description=f"Vous avez **{len(tickets)}** ticket(s) ouvert(s):", - color=discord.Color.blue() + color=disnake.Color.blue() ) for ticket in tickets[:5]: @@ -183,7 +183,7 @@ class TicketPanelView(discord.ui.View): await interaction.response.send_message(embed=embed, ephemeral=True) - async def _analytics_callback(self, interaction: discord.Interaction): + async def _analytics_callback(self, interaction: disnake.Interaction): """Affiche les analytics (staff uniquement)""" # Reload config self.config = self.storage.load_config(interaction.guild_id) @@ -200,10 +200,10 @@ class TicketPanelView(discord.ui.View): staff_list = await analytics.get_staff_list(interaction.guild_id) # Create main embed with overall stats - embed = discord.Embed( + embed = disnake.Embed( title="📊 Analytics des Tickets", description="Statistiques détaillées des tickets et performances du staff", - color=discord.Color.blue(), + color=disnake.Color.blue(), timestamp=datetime.now() ) @@ -246,16 +246,16 @@ class TicketPanelView(discord.ui.View): rating_chart = analytics.create_staff_rating_chart(staff_list) await interaction.followup.send( "📊 **Évaluations des Membres du Staff**", - file=discord.File(rating_chart, 'staff_ratings.png'), + file=disnake.File(rating_chart, 'staff_ratings.png'), ephemeral=True ) # Add individual staff stats view stats_view = StaffStatsView(self.bot, staff_list, analytics) - stats_embed = discord.Embed( + stats_embed = disnake.Embed( title="📈 Statistiques Individuelles", description="Cliquez sur un membre du staff pour voir ses statistiques détaillées:", - color=discord.Color.purple() + color=disnake.Color.purple() ) await interaction.followup.send(embed=stats_embed, view=stats_view, ephemeral=True) @@ -264,7 +264,7 @@ class TicketPanelView(discord.ui.View): - async def _config_callback(self, interaction: discord.Interaction): + async def _config_callback(self, interaction: disnake.Interaction): """Ouvre le panel de configuration (admin ou rôles config uniquement)""" # Reload config self.config = self.storage.load_config(interaction.guild_id) @@ -279,15 +279,15 @@ class TicketPanelView(discord.ui.View): storage = get_storage() config_view = ConfigPanelView(self.bot, storage, self.config) - embed = discord.Embed( + embed = disnake.Embed( title="Configuration des Tickets", description="Gérez la configuration du système de tickets", - color=discord.Color.blue() + color=disnake.Color.blue() ) await interaction.response.send_message(embed=embed, view=config_view, ephemeral=True) - async def _staff_ratings_callback(self, interaction: discord.Interaction): + async def _staff_ratings_callback(self, interaction: disnake.Interaction): """Display staff ratings (staff only)""" # Reload config self.config = self.storage.load_config(interaction.guild_id) @@ -304,10 +304,10 @@ class TicketPanelView(discord.ui.View): staff_list = await analytics.get_staff_list(interaction.guild_id) # Create main embed with overall stats - embed = discord.Embed( + embed = disnake.Embed( title="⭐ Notation du Staff", description="Performance globale du staff", - color=discord.Color.blue(), + color=disnake.Color.blue(), timestamp=datetime.now() ) @@ -350,16 +350,16 @@ class TicketPanelView(discord.ui.View): rating_chart = analytics.create_staff_rating_chart(staff_list) await interaction.followup.send( "📊 **Évaluations des Membres du Staff**", - file=discord.File(rating_chart, 'staff_ratings.png'), + file=disnake.File(rating_chart, 'staff_ratings.png'), ephemeral=True ) # Add individual staff stats view stats_view = StaffStatsView(self.bot, staff_list, analytics) - stats_embed = discord.Embed( + stats_embed = disnake.Embed( title="📈 Statistiques Individuelles", description="Cliquez sur un membre du staff pour voir ses statistiques détaillées:", - color=discord.Color.purple() + color=disnake.Color.purple() ) await interaction.followup.send(embed=stats_embed, view=stats_view, ephemeral=True) @@ -367,13 +367,13 @@ class TicketPanelView(discord.ui.View): await interaction.followup.send(f"❌ Erreur lors de la génération des graphiques: {e}", ephemeral=True) - def _is_staff(self, interaction: discord.Interaction) -> bool: + def _is_staff(self, interaction: disnake.Interaction) -> bool: return is_staff(interaction, self.config) - def _can_access_config(self, interaction: discord.Interaction) -> bool: + def _can_access_config(self, interaction: disnake.Interaction) -> bool: return can_access_config(interaction, self.config) - def _is_whitelisted(self, interaction: discord.Interaction) -> bool: + def _is_whitelisted(self, interaction: disnake.Interaction) -> bool: return is_whitelisted(interaction, self.config, self.bot) def _calculate_stats(self, guild_id: int) -> TicketStats: @@ -415,10 +415,10 @@ class TicketPanelView(discord.ui.View): return stats -class CategoryButton(discord.ui.Button): +class CategoryButton(disnake.ui.Button): def __init__(self, category, view_instance): super().__init__( - style=discord.ButtonStyle.blurple, + style=disnake.ButtonStyle.blurple, label=f"{category.emoji} {category.name}", custom_id=f"cat_{category.id}_{view_instance.id_gen}" ) @@ -426,11 +426,11 @@ class CategoryButton(discord.ui.Button): self.view_instance = view_instance # We need unique custom_ids even for ephemeral to avoid any weird collisions/caching - async def callback(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.Interaction): await self.view_instance._on_category_select(interaction, self.category.id) -class CategorySelectionView(discord.ui.View): +class CategorySelectionView(disnake.ui.View): """Sélection de catégorie pour créer un ticket""" def __init__(self, bot, config: GuildTicketConfig): @@ -443,16 +443,16 @@ class CategorySelectionView(discord.ui.View): for category in config.categories[:5]: self.add_item(CategoryButton(category, self)) - def _is_staff(self, interaction: discord.Interaction) -> bool: + def _is_staff(self, interaction: disnake.Interaction) -> bool: return is_staff(interaction, self.config) - def _can_access_config(self, interaction: discord.Interaction) -> bool: + def _can_access_config(self, interaction: disnake.Interaction) -> bool: return can_access_config(interaction, self.config) - def _is_whitelisted(self, interaction: discord.Interaction) -> bool: + def _is_whitelisted(self, interaction: disnake.Interaction) -> bool: return is_whitelisted(interaction, self.config, self.bot) - async def _on_category_select(self, interaction: discord.Interaction, category_id: str): + async def _on_category_select(self, interaction: disnake.Interaction, category_id: str): """Handle category selection""" try: @@ -486,7 +486,7 @@ class CategorySelectionView(discord.ui.View): pass -class TicketPriorityModal(discord.ui.Modal): +class TicketPriorityModal(disnake.ui.Modal): """Modal pour sélectionner la priorité et la raison""" def __init__(self, bot, config: GuildTicketConfig, category: TicketCategory): @@ -496,7 +496,7 @@ class TicketPriorityModal(discord.ui.Modal): self.category = category # Priorité - self.priority = discord.ui.TextInput( + self.priority = disnake.ui.TextInput( label="Priorité", placeholder="Normale, Haute ou Urgente", default="Normale", @@ -506,16 +506,16 @@ class TicketPriorityModal(discord.ui.Modal): self.add_item(self.priority) # Raison - self.reason = discord.ui.TextInput( + self.reason = disnake.ui.TextInput( label="Motif de votre demande", placeholder="Décrivez votre demande...", - style=discord.TextStyle.paragraph, + style=disnake.TextStyle.paragraph, required=True, max_length=500 ) self.add_item(self.reason) - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): try: # Map priority priority_map = { @@ -530,11 +530,11 @@ class TicketPriorityModal(discord.ui.Modal): except Exception as e: await interaction.response.send_message(f"Erreur lors de la création du ticket: {e}", ephemeral=True) - async def on_error(self, interaction: discord.Interaction, error: Exception): + async def on_error(self, interaction: disnake.Interaction, error: Exception): """Handle errors in the modal""" await interaction.response.send_message("Une erreur inattendue s'est produite lors de la création du ticket.", ephemeral=True) - async def _create_ticket(self, interaction: discord.Interaction, priority: Priority, reason: str): + async def _create_ticket(self, interaction: disnake.Interaction, priority: Priority, reason: str): """Crée le ticket""" guild = interaction.guild user = interaction.user @@ -543,9 +543,9 @@ class TicketPriorityModal(discord.ui.Modal): # Build permissions overwrites = { - guild.default_role: discord.PermissionOverwrite(read_messages=False), - user: discord.PermissionOverwrite(read_messages=True, send_messages=True), - guild.me: discord.PermissionOverwrite(read_messages=True, send_messages=True) + guild.default_role: disnake.PermissionOverwrite(read_messages=False), + user: disnake.PermissionOverwrite(read_messages=True, send_messages=True), + guild.me: disnake.PermissionOverwrite(read_messages=True, send_messages=True) } # Add staff roles (category-specific roles override global ones) @@ -554,7 +554,7 @@ class TicketPriorityModal(discord.ui.Modal): try: role = guild.get_role(int(role_id)) if role and role not in overwrites: - overwrites[role] = discord.PermissionOverwrite(read_messages=True, send_messages=True) + overwrites[role] = disnake.PermissionOverwrite(read_messages=True, send_messages=True) except (ValueError, TypeError): continue @@ -605,10 +605,10 @@ class TicketPriorityModal(discord.ui.Modal): storage.save_ticket(ticket_data) # Send welcome message - embed = discord.Embed( + embed = disnake.Embed( title=f" {self.category.emoji} {self.category.name}", description=f"Bienvenue {user.mention}!\n\nVotre demande sera traitée sous peu par notre équipe.", - color=discord.Color.blurple() + color=disnake.Color.blurple() ) embed.add_field(name="Motif", value=reason, inline=False) embed.add_field(name="Priorité", value=priority.value.capitalize(), inline=True) @@ -633,9 +633,9 @@ class TicketPriorityModal(discord.ui.Modal): if self.config.log_channel_id: log_channel = guild.get_channel(self.config.log_channel_id) if log_channel: - log_embed = discord.Embed( + log_embed = disnake.Embed( title="Nouveau Ticket", - color=discord.Color.green(), + color=disnake.Color.green(), timestamp=datetime.now() ) log_embed.add_field(name="Utilisateur", value=user.mention, inline=True) @@ -646,7 +646,7 @@ class TicketPriorityModal(discord.ui.Modal): await interaction.response.send_message(f"Ticket créé: {ticket_channel.mention}", ephemeral=True) -class SetCategoryModal(discord.ui.Modal): +class SetCategoryModal(disnake.ui.Modal): """Modal pour définir la catégorie Discord où les tickets seront créés""" def __init__(self, bot, storage, config: GuildTicketConfig): @@ -655,7 +655,7 @@ class SetCategoryModal(discord.ui.Modal): self.storage = storage self.config = config - self.category = discord.ui.TextInput( + self.category = disnake.ui.TextInput( label="Catégorie Discord", placeholder="Entrez l'ID de la catégorie (clic droit -> Copier l'ID)", required=True, @@ -663,7 +663,7 @@ class SetCategoryModal(discord.ui.Modal): ) self.add_item(self.category) - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): cat_id = self.category.value.strip() # Verify it's a valid ID @@ -673,7 +673,7 @@ class SetCategoryModal(discord.ui.Modal): # Check if category exists category = interaction.guild.get_channel(int(cat_id)) - if not category or not isinstance(category, discord.CategoryChannel): + if not category or not isinstance(category, disnake.CategoryChannel): await interaction.response.send_message("❌ Catégorie introuvable ou ce n'est pas une catégorie.", ephemeral=True) return @@ -683,7 +683,7 @@ class SetCategoryModal(discord.ui.Modal): await interaction.response.send_message(f"📁 Les tickets seront désormais créés dans la catégorie **{category.name}**!", ephemeral=True) -class TicketManagementView(discord.ui.View): +class TicketManagementView(disnake.ui.View): """Gestion du ticket (dans le salon du ticket)""" def __init__(self, bot, storage, config: GuildTicketConfig, ticket: TicketData): @@ -696,8 +696,8 @@ class TicketManagementView(discord.ui.View): # Claim (if enabled and ticket is open or claimed) if config.claim_enabled and self.category and self.category.allow_claims and ticket.status in [TicketStatus.OPEN, TicketStatus.CLAIMED]: - claim_btn = discord.ui.Button( - style=discord.ButtonStyle.primary, + claim_btn = disnake.ui.Button( + style=disnake.ButtonStyle.primary, label="Réclamer" if ticket.status == TicketStatus.OPEN else "Transférer", custom_id="ticket_claim" ) @@ -705,8 +705,8 @@ class TicketManagementView(discord.ui.View): self.add_item(claim_btn) # Fermer - close_btn = discord.ui.Button( - style=discord.ButtonStyle.red, + close_btn = disnake.ui.Button( + style=disnake.ButtonStyle.red, label="Fermer", custom_id="ticket_close" ) @@ -714,8 +714,8 @@ class TicketManagementView(discord.ui.View): self.add_item(close_btn) # Transcripts - transcript_btn = discord.ui.Button( - style=discord.ButtonStyle.secondary, + transcript_btn = disnake.ui.Button( + style=disnake.ButtonStyle.secondary, label="Transcript", custom_id="ticket_transcript" ) @@ -724,15 +724,15 @@ class TicketManagementView(discord.ui.View): # Reopen (if closed) if ticket.status == TicketStatus.CLOSED: - reopen_btn = discord.ui.Button( - style=discord.ButtonStyle.green, + reopen_btn = disnake.ui.Button( + style=disnake.ButtonStyle.green, label="Rouvrir", custom_id="ticket_reopen" ) reopen_btn.callback = self._reopen_callback self.add_item(reopen_btn) - async def _close_callback(self, interaction: discord.Interaction): + async def _close_callback(self, interaction: disnake.Interaction): """Demande confirmation avant de fermer le ticket""" # Vérifier si l'utilisateur peut fermer le ticket can_close = ( @@ -745,11 +745,11 @@ class TicketManagementView(discord.ui.View): return # Créer l'embed de confirmation - embed = discord.Embed( + embed = disnake.Embed( title="🔒 Confirmer la Fermeture", description="Êtes-vous sûr de vouloir fermer ce ticket?\n\n" "Cette action est irréversible et le salon sera supprimé.", - color=discord.Color.orange() + color=disnake.Color.orange() ) # Ajouter les informations du ticket @@ -766,7 +766,7 @@ class TicketManagementView(discord.ui.View): view = CloseConfirmationView(self.bot, self.storage, self.config, self.ticket) await interaction.response.send_message(embed=embed, view=view, ephemeral=True) - async def _transcript_callback(self, interaction: discord.Interaction): + async def _transcript_callback(self, interaction: disnake.Interaction): """Génère le transcript""" if not self._is_staff(interaction): await interaction.response.send_message("Réservé au staff.", ephemeral=True) @@ -798,12 +798,12 @@ class TicketManagementView(discord.ui.View): filepath = transcript_gen.save_transcript(ticket, messages, format="html") if filepath and os.path.exists(filepath): - file = discord.File(filepath, filename=os.path.basename(filepath)) + file = disnake.File(filepath, filename=os.path.basename(filepath)) await interaction.response.send_message("Transcript généré:", file=file, ephemeral=True) else: await interaction.response.send_message("Erreur lors de la génération du transcript.", ephemeral=True) - async def _claim_callback(self, interaction: discord.Interaction): + async def _claim_callback(self, interaction: disnake.Interaction): """Réclame le ticket""" if not self._is_staff(interaction): await interaction.response.send_message("Réservé au staff.", ephemeral=True) @@ -828,17 +828,17 @@ class TicketManagementView(discord.ui.View): await interaction.response.edit_message(view=self) if old_claimer: - embed = discord.Embed( + embed = disnake.Embed( title="🔄 Ticket Transféré", description=f"Le ticket a été transféré de <@{old_claimer}> à {interaction.user.mention}", - color=discord.Color.blue() + color=disnake.Color.blue() ) else: embed = TicketEmbedFormatter.ticket_claimed(self.ticket, interaction.user) await interaction.channel.send(embed=embed) - async def _reopen_callback(self, interaction: discord.Interaction): + async def _reopen_callback(self, interaction: disnake.Interaction): """Rouvre le ticket""" if not self._is_staff(interaction): await interaction.response.send_message("Réservé au staff.", ephemeral=True) @@ -852,8 +852,8 @@ class TicketManagementView(discord.ui.View): # Update channel overwrites = interaction.channel.overwrites.copy() for target, perms in overwrites.items(): - if isinstance(target, discord.Member) and target.id != interaction.guild.me.id: - overwrites[target] = discord.PermissionOverwrite( + if isinstance(target, disnake.Member) and target.id != interaction.guild.me.id: + overwrites[target] = disnake.PermissionOverwrite( read_messages=True, send_messages=True ) @@ -870,8 +870,8 @@ class TicketManagementView(discord.ui.View): break if self.config.claim_enabled and self.category and self.category.allow_claims: - claim_btn = discord.ui.Button( - style=discord.ButtonStyle.primary, + claim_btn = disnake.ui.Button( + style=disnake.ButtonStyle.primary, label="Réclamer", custom_id="ticket_claim" ) @@ -882,7 +882,7 @@ class TicketManagementView(discord.ui.View): await interaction.channel.send(embed=embed) await interaction.response.send_message("Ticket rouvert!", ephemeral=True) - def _is_staff(self, interaction: discord.Interaction) -> bool: + def _is_staff(self, interaction: disnake.Interaction) -> bool: return is_staff(interaction, self.config, self.category) def _is_staff_member(self, member) -> bool: @@ -891,7 +891,7 @@ class TicketManagementView(discord.ui.View): return False # Handle cases where member is a User (left the guild) - if not isinstance(member, discord.Member): + if not isinstance(member, disnake.Member): return member.id in self.config.staff_users if member.guild_permissions.administrator: @@ -912,7 +912,7 @@ class TicketManagementView(discord.ui.View): return False -class CloseConfirmationView(discord.ui.View): +class CloseConfirmationView(disnake.ui.View): """Demande de confirmation avant la fermeture du ticket""" def __init__(self, bot, storage, config: GuildTicketConfig, ticket: TicketData): @@ -923,8 +923,8 @@ class CloseConfirmationView(discord.ui.View): self.ticket = ticket self.category = config.get_category(ticket.category_id) - @discord.ui.button(label="✅ Confirmer", style=discord.ButtonStyle.green, custom_id="close_confirm") - async def confirm_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="✅ Confirmer", style=disnake.ButtonStyle.green, custom_id="close_confirm") + async def confirm_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): """L'utilisateur confirme la fermeture""" # Vérifier les permissions can_close = ( @@ -937,13 +937,13 @@ class CloseConfirmationView(discord.ui.View): return # Show the delay view with countdown - embed = discord.Embed( + embed = disnake.Embed( title="⏳ Fermeture du Ticket", description="Le ticket va être fermé dans quelques secondes...\n\n" "📄 Génération du transcript en cours...\n" "📝 Envoi aux logs...\n" "🗑️ Suppression du canal...", - color=discord.Color.orange() + color=disnake.Color.orange() ) view = CloseDelayView(self.bot, self.storage, self.config, self.ticket) @@ -952,17 +952,17 @@ class CloseConfirmationView(discord.ui.View): # Start the countdown await view.start_countdown(interaction) - @discord.ui.button(label="❌ Annuler", style=discord.ButtonStyle.red, custom_id="close_cancel") - async def cancel_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="❌ Annuler", style=disnake.ButtonStyle.red, custom_id="close_cancel") + async def cancel_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): """L'utilisateur annule la fermeture""" await interaction.response.defer() await interaction.delete_original_response() - def _is_staff(self, interaction: discord.Interaction) -> bool: + def _is_staff(self, interaction: disnake.Interaction) -> bool: return is_staff(interaction, self.config, self.category) -class CloseDelayView(discord.ui.View): +class CloseDelayView(disnake.ui.View): """Affiche le délai d'attente avant suppression du ticket""" def __init__(self, bot, storage, config: GuildTicketConfig, ticket: TicketData, close_reason: str = ""): @@ -975,15 +975,15 @@ class CloseDelayView(discord.ui.View): self.category = config.get_category(ticket.category_id) # Ajouter le bouton Annuler - cancel_btn = discord.ui.Button( + cancel_btn = disnake.ui.Button( label="❌ Annuler", - style=discord.ButtonStyle.red, + style=disnake.ButtonStyle.red, custom_id="delay_cancel" ) cancel_btn.callback = self._cancel_callback self.add_item(cancel_btn) - async def start_countdown(self, interaction: discord.Interaction): + async def start_countdown(self, interaction: disnake.Interaction): """Démarre le compte à rebours""" # Désactiver le bouton Annuler for item in self.children: @@ -999,11 +999,11 @@ class CloseDelayView(discord.ui.View): ] for step_text, delay in steps: - embed = discord.Embed( + embed = disnake.Embed( title="🔒 Fermeture du Ticket", description=f"**{step_text}**\n\n" f"Merci pour votre confiance!", - color=discord.Color.orange() + color=disnake.Color.orange() ) embed.set_footer(text=f"Ticket #{self.ticket.channel_id}") @@ -1013,7 +1013,7 @@ class CloseDelayView(discord.ui.View): # Procéder à la fermeture await self._close_ticket(interaction) - async def _close_ticket(self, interaction: discord.Interaction): + async def _close_ticket(self, interaction: disnake.Interaction): """Effectue la fermeture réelle du ticket""" # Collect messages for transcript transcript_file = None @@ -1052,10 +1052,10 @@ class CloseDelayView(discord.ui.View): if user: from .feedback_view import FeedbackView - dm_embed = discord.Embed( + dm_embed = disnake.Embed( title="📝 Votre avis nous intéresse !", description=f"Votre ticket **#{self.ticket.channel_id}** a été fermé.\nMerci de prendre un moment pour noter la qualité du support.", - color=discord.Color.green(), + color=disnake.Color.green(), timestamp=datetime.now() ) dm_embed.add_field(name="Durée du ticket", value=f"{duration:.1f} minutes", inline=True) @@ -1066,9 +1066,9 @@ class CloseDelayView(discord.ui.View): view = FeedbackView(self.bot, self.storage, self.ticket) # Add a button to directly open the feedback form - note_staff_btn = discord.ui.Button( + note_staff_btn = disnake.ui.Button( label="⭐ Noter le staff", - style=discord.ButtonStyle.primary, + style=disnake.ButtonStyle.primary, custom_id="note_staff_direct" ) note_staff_btn.callback = self._note_staff_callback @@ -1083,10 +1083,10 @@ class CloseDelayView(discord.ui.View): try: log_channel = interaction.guild.get_channel(self.config.log_channel_id) if log_channel: - log_embed = discord.Embed( + log_embed = disnake.Embed( title="📄 Ticket Fermé - Transcript", description=f"Ticket #{self.ticket.channel_id} fermé par {interaction.user.mention}", - color=discord.Color.red(), + color=disnake.Color.red(), timestamp=datetime.now() ) @@ -1101,7 +1101,7 @@ class CloseDelayView(discord.ui.View): log_embed.add_field(name="Raison", value=self.close_reason, inline=False) if transcript_file and os.path.exists(transcript_file): - file = discord.File(transcript_file, filename=os.path.basename(transcript_file)) + file = disnake.File(transcript_file, filename=os.path.basename(transcript_file)) await log_channel.send(embed=log_embed, file=file) else: log_embed.add_field(name="⚠️ Transcript", value="Le transcript n'a pas pu être généré (l'utilisateur a peut-être quitté le serveur).", inline=False) @@ -1114,32 +1114,32 @@ class CloseDelayView(discord.ui.View): await interaction.channel.delete(reason=f"Ticket fermé par {interaction.user}") except Exception as e: print(f"Error deleting channel: {e}") - embed = discord.Embed( + embed = disnake.Embed( title="❌ Erreur", description="Le ticket a été fermé mais la suppression du salon a échoué.", - color=discord.Color.red() + color=disnake.Color.red() ) await interaction.channel.send(embed=embed) - async def _note_staff_callback(self, interaction: discord.Interaction): + async def _note_staff_callback(self, interaction: disnake.Interaction): """Handle direct staff rating button""" # This would open a direct feedback form await interaction.response.send_message("Vous pouvez maintenant noter le staff directement !", ephemeral=True) - async def _cancel_callback(self, interaction: discord.Interaction): + async def _cancel_callback(self, interaction: disnake.Interaction): """Annule la fermeture du ticket pendant le délai""" await interaction.response.defer() await interaction.delete_original_response() # Envoyer un message de confirmation d'annulation - embed = discord.Embed( + embed = disnake.Embed( title="❌ Fermeture Annulée", description="La fermeture du ticket a été annulée.", - color=discord.Color.red() + color=disnake.Color.red() ) await interaction.channel.send(embed=embed, delete_after=5) - def _is_staff(self, interaction: discord.Interaction) -> bool: + def _is_staff(self, interaction: disnake.Interaction) -> bool: return is_staff(interaction, self.config, self.category) def _is_staff_member(self, member) -> bool: @@ -1148,7 +1148,7 @@ class CloseDelayView(discord.ui.View): return False # Handle cases where member is a User (left the guild) - if not isinstance(member, discord.Member): + if not isinstance(member, disnake.Member): return member.id in self.config.staff_users if member.guild_permissions.administrator: @@ -1169,7 +1169,7 @@ class CloseDelayView(discord.ui.View): return False -class CloseTicketModal(discord.ui.Modal): +class CloseTicketModal(disnake.ui.Modal): """Modal pour fermer un ticket""" def __init__(self, bot, storage, config: GuildTicketConfig, ticket: TicketData): @@ -1180,10 +1180,10 @@ class CloseTicketModal(discord.ui.Modal): self.ticket = ticket self.category = config.get_category(ticket.category_id) - self.reason = discord.ui.TextInput( + self.reason = disnake.ui.TextInput( label="Raison de fermeture", placeholder="Optionnel...", - style=discord.TextStyle.paragraph, + style=disnake.TextStyle.paragraph, required=False, max_length=300 ) @@ -1191,7 +1191,7 @@ class CloseTicketModal(discord.ui.Modal): # Survey fields if enabled if config.survey_enabled and self.category and self.category.survey_enabled: - self.rating = discord.ui.TextInput( + self.rating = disnake.ui.TextInput( label="Note (1-5 étoiles)", placeholder="1 à 5", required=False, @@ -1199,16 +1199,16 @@ class CloseTicketModal(discord.ui.Modal): ) self.add_item(self.rating) - self.feedback = discord.ui.TextInput( + self.feedback = disnake.ui.TextInput( label="Commentaires", placeholder="Vos commentaires sur le support...", - style=discord.TextStyle.paragraph, + style=disnake.TextStyle.paragraph, required=False, max_length=500 ) self.add_item(self.feedback) - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): # Check permissions can_close = ( interaction.user.id == self.ticket.user_id or @@ -1239,13 +1239,13 @@ class CloseTicketModal(discord.ui.Modal): await interaction.response.send_message("✅ Fermeture du ticket en cours...", ephemeral=True) # Send confirmation message in the ticket channel - confirm_embed = discord.Embed( + confirm_embed = disnake.Embed( title="⏳ Fermeture du Ticket", description="Le ticket va être fermé dans quelques secondes...\n\n" "📄 Génération du transcript en cours...\n" "📝 Envoi aux logs...\n" "🗑️ Suppression du canal...", - color=discord.Color.orange() + color=disnake.Color.orange() ) await interaction.channel.send(embed=confirm_embed) @@ -1290,10 +1290,10 @@ class CloseTicketModal(discord.ui.Modal): log_channel = interaction.guild.get_channel(self.config.log_channel_id) if log_channel: # Create log embed - log_embed = discord.Embed( + log_embed = disnake.Embed( title="📄 Ticket Fermé - Transcript", description=f"Ticket #{self.ticket.channel_id} fermé par {interaction.user.mention}", - color=discord.Color.red(), + color=disnake.Color.red(), timestamp=datetime.now() ) @@ -1310,7 +1310,7 @@ class CloseTicketModal(discord.ui.Modal): # Send transcript file if transcript_file and os.path.exists(transcript_file): - file = discord.File(transcript_file, filename=os.path.basename(transcript_file)) + file = disnake.File(transcript_file, filename=os.path.basename(transcript_file)) await log_channel.send(embed=log_embed, file=file) else: log_embed.add_field(name="⚠️ Transcript", value="Le transcript n'a pas pu être généré (l'utilisateur a peut-être quitté le serveur).", inline=False) @@ -1326,7 +1326,7 @@ class CloseTicketModal(discord.ui.Modal): # Fallback: just mark as closed if deletion fails await interaction.followup.send("Erreur lors de la suppression du canal, mais le ticket a été fermé.", ephemeral=True) - def _is_staff(self, interaction: discord.Interaction) -> bool: + def _is_staff(self, interaction: disnake.Interaction) -> bool: return is_staff(interaction, self.config, self.category) def _is_staff_member(self, member) -> bool: @@ -1335,7 +1335,7 @@ class CloseTicketModal(discord.ui.Modal): return False # Handle cases where member is a User (left the guild) - if not isinstance(member, discord.Member): + if not isinstance(member, disnake.Member): return member.id in self.config.staff_users if member.guild_permissions.administrator: @@ -1355,7 +1355,7 @@ class CloseTicketModal(discord.ui.Modal): return False -class TicketSetupView(discord.ui.View): +class TicketSetupView(disnake.ui.View): """Panel de configuration complet du système de tickets""" def __init__(self, bot, storage, config: GuildTicketConfig): @@ -1365,8 +1365,8 @@ class TicketSetupView(discord.ui.View): self.config = config # Toggle enabled - toggle_btn = discord.ui.Button( - style=discord.ButtonStyle.green if config.enabled else discord.ButtonStyle.red, + toggle_btn = disnake.ui.Button( + style=disnake.ButtonStyle.green if config.enabled else disnake.ButtonStyle.red, label="🔄 Activer/Désactiver", custom_id="setup_toggle" ) @@ -1374,8 +1374,8 @@ class TicketSetupView(discord.ui.View): self.add_item(toggle_btn) # Staff roles - roles_btn = discord.ui.Button( - style=discord.ButtonStyle.primary, + roles_btn = disnake.ui.Button( + style=disnake.ButtonStyle.primary, label="👥 Rôles Staff", custom_id="setup_roles" ) @@ -1383,8 +1383,8 @@ class TicketSetupView(discord.ui.View): self.add_item(roles_btn) # Config roles - config_roles_btn = discord.ui.Button( - style=discord.ButtonStyle.secondary, + config_roles_btn = disnake.ui.Button( + style=disnake.ButtonStyle.secondary, label="🔑 Rôles Config", custom_id="setup_config_roles" ) @@ -1392,8 +1392,8 @@ class TicketSetupView(discord.ui.View): self.add_item(config_roles_btn) # Categories - cat_btn = discord.ui.Button( - style=discord.ButtonStyle.secondary, + cat_btn = disnake.ui.Button( + style=disnake.ButtonStyle.secondary, label="📂 Catégories", custom_id="setup_categories" ) @@ -1401,8 +1401,8 @@ class TicketSetupView(discord.ui.View): self.add_item(cat_btn) # Log channel - log_btn = discord.ui.Button( - style=discord.ButtonStyle.secondary, + log_btn = disnake.ui.Button( + style=disnake.ButtonStyle.secondary, label="📝 Canal de Logs", custom_id="setup_log_channel" ) @@ -1410,36 +1410,36 @@ class TicketSetupView(discord.ui.View): self.add_item(log_btn) # Advanced settings - advanced_btn = discord.ui.Button( - style=discord.ButtonStyle.secondary, + advanced_btn = disnake.ui.Button( + style=disnake.ButtonStyle.secondary, label="⚙️ Paramètres Avancés", custom_id="setup_advanced" ) advanced_btn.callback = self._advanced_callback self.add_item(advanced_btn) - async def _toggle_callback(self, interaction: discord.Interaction): + async def _toggle_callback(self, interaction: disnake.Interaction): self.config.enabled = not self.config.enabled self.storage.save_config(interaction.guild_id, self.config) status = "activé" if self.config.enabled else "désactivé" - embed = discord.Embed( + embed = disnake.Embed( title="🔄 État du Système", description=f"Le système de tickets a été **{status}**!", - color=discord.Color.green() if self.config.enabled else discord.Color.red() + color=disnake.Color.green() if self.config.enabled else disnake.Color.red() ) await interaction.response.send_message(embed=embed, ephemeral=True) # Update button for item in self.children: if item.custom_id == "setup_toggle": - item.style = discord.ButtonStyle.green if self.config.enabled else discord.ButtonStyle.red + item.style = disnake.ButtonStyle.green if self.config.enabled else disnake.ButtonStyle.red await interaction.message.edit(view=self) - async def _roles_callback(self, interaction: discord.Interaction): + async def _roles_callback(self, interaction: disnake.Interaction): view = StaffRolesSetupView(self.bot, self.storage, self.config, interaction.guild) - embed = discord.Embed( + embed = disnake.Embed( title="👥 Configuration des Rôles Staff", description="Définissez les rôles qui peuvent gérer les tickets et accéder aux fonctionnalités avancées.\n\n" "**Permissions des rôles staff:**\n" @@ -1447,7 +1447,7 @@ class TicketSetupView(discord.ui.View): "• Accéder aux analytics et statistiques\n" "• Générer des transcripts\n" "• Fermer les tickets des autres", - color=discord.Color.blue() + color=disnake.Color.blue() ) role_mentions = [] @@ -1464,13 +1464,13 @@ class TicketSetupView(discord.ui.View): await interaction.response.send_message(embed=embed, view=view, ephemeral=True) - async def _categories_callback(self, interaction: discord.Interaction): + async def _categories_callback(self, interaction: disnake.Interaction): view = CategoriesSetupView(self.bot, self.storage, self.config) - embed = discord.Embed( + embed = disnake.Embed( title="📂 Gestion des Catégories", description="Créez et gérez les catégories de tickets pour organiser vos demandes.", - color=discord.Color.blue() + color=disnake.Color.blue() ) if self.config.categories: @@ -1491,17 +1491,17 @@ class TicketSetupView(discord.ui.View): await interaction.response.send_message(embed=embed, view=view, ephemeral=True) - async def _config_roles_callback(self, interaction: discord.Interaction): + async def _config_roles_callback(self, interaction: disnake.Interaction): view = ConfigRolesSetupView(self.bot, self.storage, self.config, interaction.guild) - embed = discord.Embed( + embed = disnake.Embed( title="🔑 Configuration des Rôles Config", description="Définissez les rôles qui peuvent accéder au panel de configuration du système de tickets.\n\n" "**Permissions des rôles config:**\n" "• Accéder à la commande /ticketconfig\n" "• Modifier tous les paramètres du système\n" "• Gérer les catégories et rôles staff", - color=discord.Color.purple() + color=disnake.Color.purple() ) role_mentions = [] @@ -1518,17 +1518,17 @@ class TicketSetupView(discord.ui.View): await interaction.response.send_message(embed=embed, view=view, ephemeral=True) - async def _log_channel_callback(self, interaction: discord.Interaction): + async def _log_channel_callback(self, interaction: disnake.Interaction): modal = SetChannelModal(self.bot, self.storage, self.config, "log") await interaction.response.send_modal(modal) - async def _advanced_callback(self, interaction: discord.Interaction): + async def _advanced_callback(self, interaction: disnake.Interaction): view = AdvancedSetupView(self.bot, self.storage, self.config) - embed = discord.Embed( + embed = disnake.Embed( title="⚙️ Paramètres Avancés", description="Configuration fine du système de tickets.", - color=discord.Color.purple() + color=disnake.Color.purple() ) embed.add_field( @@ -1542,10 +1542,10 @@ class TicketSetupView(discord.ui.View): await interaction.response.send_message(embed=embed, view=view, ephemeral=True) -class StaffRolesSetupView(discord.ui.View): +class StaffRolesSetupView(disnake.ui.View): """Configuration des rôles staff""" - def __init__(self, bot, storage, config: GuildTicketConfig, guild: discord.Guild): + def __init__(self, bot, storage, config: GuildTicketConfig, guild: disnake.Guild): super().__init__(timeout=None) self.bot = bot self.storage = storage @@ -1559,7 +1559,7 @@ class StaffRolesSetupView(discord.ui.View): for role in self.guild.roles: if role.id != self.guild.id: # Exclude @everyone options.append( - discord.SelectOption( + disnake.SelectOption( label=role.name, value=str(role.id), default=str(role.id) in self.config.staff_roles @@ -1567,7 +1567,7 @@ class StaffRolesSetupView(discord.ui.View): ) if options: - select = discord.ui.Select( + select = disnake.ui.Select( placeholder="Sélectionnez les rôles staff...", options=options[:25], min_values=0, @@ -1576,7 +1576,7 @@ class StaffRolesSetupView(discord.ui.View): select.callback = self._select_callback self.add_item(select) - async def _select_callback(self, interaction: discord.Interaction): + async def _select_callback(self, interaction: disnake.Interaction): selected = list(interaction.data.get("values", [])) self.config.staff_roles = selected @@ -1588,10 +1588,10 @@ class StaffRolesSetupView(discord.ui.View): if role: role_mentions.append(role.mention) - embed = discord.Embed( + embed = disnake.Embed( title="✅ Rôles Staff Mis à Jour", description=f"**{len(selected)}** rôle(s) configuré(s) comme staff.", - color=discord.Color.green() + color=disnake.Color.green() ) embed.add_field( @@ -1603,10 +1603,10 @@ class StaffRolesSetupView(discord.ui.View): await interaction.response.send_message(embed=embed, ephemeral=True) -class ConfigRolesSetupView(discord.ui.View): +class ConfigRolesSetupView(disnake.ui.View): """Configuration des rôles config""" - def __init__(self, bot, storage, config: GuildTicketConfig, guild: discord.Guild): + def __init__(self, bot, storage, config: GuildTicketConfig, guild: disnake.Guild): super().__init__(timeout=None) self.bot = bot self.storage = storage @@ -1620,7 +1620,7 @@ class ConfigRolesSetupView(discord.ui.View): for role in self.guild.roles: if role.id != self.guild.id: # Exclude @everyone options.append( - discord.SelectOption( + disnake.SelectOption( label=role.name, value=str(role.id), default=str(role.id) in self.config.config_roles @@ -1628,7 +1628,7 @@ class ConfigRolesSetupView(discord.ui.View): ) if options: - select = discord.ui.Select( + select = disnake.ui.Select( placeholder="Sélectionnez les rôles config...", options=options[:25], min_values=0, @@ -1637,7 +1637,7 @@ class ConfigRolesSetupView(discord.ui.View): select.callback = self._select_callback self.add_item(select) - async def _select_callback(self, interaction: discord.Interaction): + async def _select_callback(self, interaction: disnake.Interaction): selected = list(interaction.data.get("values", [])) self.config.config_roles = selected @@ -1649,10 +1649,10 @@ class ConfigRolesSetupView(discord.ui.View): if role: role_mentions.append(role.mention) - embed = discord.Embed( + embed = disnake.Embed( title="✅ Rôles Config Mis à Jour", description=f"**{len(selected)}** rôle(s) configuré(s) pour accéder à la configuration.", - color=discord.Color.green() + color=disnake.Color.green() ) embed.add_field( @@ -1670,7 +1670,7 @@ class ConfigRolesSetupView(discord.ui.View): await interaction.response.send_message(embed=embed, ephemeral=True) -class CategoriesSetupView(discord.ui.View): +class CategoriesSetupView(disnake.ui.View): """Gestion des catégories""" def __init__(self, bot, storage, config: GuildTicketConfig): @@ -1680,8 +1680,8 @@ class CategoriesSetupView(discord.ui.View): self.config = config # Add category - add_btn = discord.ui.Button( - style=discord.ButtonStyle.green, + add_btn = disnake.ui.Button( + style=disnake.ButtonStyle.green, label="➕ Ajouter", custom_id="cat_add" ) @@ -1690,19 +1690,19 @@ class CategoriesSetupView(discord.ui.View): # Remove category (only if categories exist) if config.categories: - remove_btn = discord.ui.Button( - style=discord.ButtonStyle.red, + remove_btn = disnake.ui.Button( + style=disnake.ButtonStyle.red, label="➖ Supprimer", custom_id="cat_remove" ) remove_btn.callback = self._remove_callback self.add_item(remove_btn) - async def _add_callback(self, interaction: discord.Interaction): + async def _add_callback(self, interaction: disnake.Interaction): modal = AddCategoryModal(self.bot, self.storage, self.config) await interaction.response.send_modal(modal) - async def _remove_callback(self, interaction: discord.Interaction): + async def _remove_callback(self, interaction: disnake.Interaction): if not self.config.categories: await interaction.response.send_message("Aucune catégorie à supprimer.", ephemeral=True) return @@ -1711,31 +1711,31 @@ class CategoriesSetupView(discord.ui.View): options = [] for cat in self.config.categories[:25]: # Discord limit options.append( - discord.SelectOption( + disnake.SelectOption( label=cat.name, value=cat.id, emoji=cat.emoji ) ) - select = discord.ui.Select( + select = disnake.ui.Select( placeholder="Sélectionnez une catégorie à supprimer...", options=options ) select.callback = self._remove_select_callback - view = discord.ui.View() + view = disnake.ui.View() view.add_item(select) - embed = discord.Embed( + embed = disnake.Embed( title="Supprimer une Catégorie", description="Sélectionnez la catégorie à supprimer:", - color=discord.Color.red() + color=disnake.Color.red() ) await interaction.response.send_message(embed=embed, view=view, ephemeral=True) - async def _remove_select_callback(self, interaction: discord.Interaction): + async def _remove_select_callback(self, interaction: disnake.Interaction): cat_id = interaction.data["values"][0] # Remove category @@ -1745,7 +1745,7 @@ class CategoriesSetupView(discord.ui.View): await interaction.response.send_message("✅ Catégorie supprimée!", ephemeral=True) -class AdvancedSetupView(discord.ui.View): +class AdvancedSetupView(disnake.ui.View): """Paramètres avancés""" def __init__(self, bot, storage, config: GuildTicketConfig): @@ -1755,8 +1755,8 @@ class AdvancedSetupView(discord.ui.View): self.config = config # Toggle claim system - claim_btn = discord.ui.Button( - style=discord.ButtonStyle.green if config.claim_enabled else discord.ButtonStyle.red, + claim_btn = disnake.ui.Button( + style=disnake.ButtonStyle.green if config.claim_enabled else disnake.ButtonStyle.red, label=f"{'✅' if config.claim_enabled else '❌'} Système de Réclamation", custom_id="advanced_claim" ) @@ -1764,8 +1764,8 @@ class AdvancedSetupView(discord.ui.View): self.add_item(claim_btn) # Toggle survey system - survey_btn = discord.ui.Button( - style=discord.ButtonStyle.green if config.survey_enabled else discord.ButtonStyle.red, + survey_btn = disnake.ui.Button( + style=disnake.ButtonStyle.green if config.survey_enabled else disnake.ButtonStyle.red, label=f"{'✅' if config.survey_enabled else '❌'} Sondages", custom_id="advanced_survey" ) @@ -1773,58 +1773,58 @@ class AdvancedSetupView(discord.ui.View): self.add_item(survey_btn) # Max tickets per user - max_btn = discord.ui.Button( - style=discord.ButtonStyle.secondary, + max_btn = disnake.ui.Button( + style=disnake.ButtonStyle.secondary, label=f"📊 Limite: {config.max_tickets_per_user}", custom_id="advanced_max" ) max_btn.callback = self._max_callback self.add_item(max_btn) - async def _claim_callback(self, interaction: discord.Interaction): + async def _claim_callback(self, interaction: disnake.Interaction): self.config.claim_enabled = not self.config.claim_enabled self.storage.save_config(interaction.guild_id, self.config) status = "activé" if self.config.claim_enabled else "désactivé" - embed = discord.Embed( + embed = disnake.Embed( title="🔄 Système de Réclamation", description=f"Le système de réclamation a été **{status}**!", - color=discord.Color.green() if self.config.claim_enabled else discord.Color.red() + color=disnake.Color.green() if self.config.claim_enabled else disnake.Color.red() ) await interaction.response.send_message(embed=embed, ephemeral=True) # Update button for item in self.children: if item.custom_id == "advanced_claim": - item.style = discord.ButtonStyle.green if self.config.claim_enabled else discord.ButtonStyle.red + item.style = disnake.ButtonStyle.green if self.config.claim_enabled else disnake.ButtonStyle.red item.label = f"{'✅' if self.config.claim_enabled else '❌'} Système de Réclamation" await interaction.message.edit(view=self) - async def _survey_callback(self, interaction: discord.Interaction): + async def _survey_callback(self, interaction: disnake.Interaction): self.config.survey_enabled = not self.config.survey_enabled self.storage.save_config(interaction.guild_id, self.config) status = "activé" if self.config.survey_enabled else "désactivé" - embed = discord.Embed( + embed = disnake.Embed( title="🔄 Système de Sondages", description=f"Le système de sondages a été **{status}**!", - color=discord.Color.green() if self.config.survey_enabled else discord.Color.red() + color=disnake.Color.green() if self.config.survey_enabled else disnake.Color.red() ) await interaction.response.send_message(embed=embed, ephemeral=True) # Update button for item in self.children: if item.custom_id == "advanced_survey": - item.style = discord.ButtonStyle.green if self.config.survey_enabled else discord.ButtonStyle.red + item.style = disnake.ButtonStyle.green if self.config.survey_enabled else disnake.ButtonStyle.red item.label = f"{'✅' if self.config.survey_enabled else '❌'} Sondages" await interaction.message.edit(view=self) - async def _max_callback(self, interaction: discord.Interaction): + async def _max_callback(self, interaction: disnake.Interaction): modal = MaxTicketsModal(self.bot, self.storage, self.config) await interaction.response.send_modal(modal) -class MaxTicketsModal(discord.ui.Modal): +class MaxTicketsModal(disnake.ui.Modal): """Modal pour définir la limite de tickets par utilisateur""" def __init__(self, bot, storage, config: GuildTicketConfig): @@ -1833,7 +1833,7 @@ class MaxTicketsModal(discord.ui.Modal): self.storage = storage self.config = config - self.max_tickets = discord.ui.TextInput( + self.max_tickets = disnake.ui.TextInput( label="Nombre maximum de tickets ouverts par utilisateur", placeholder="5", default=str(config.max_tickets_per_user), @@ -1842,7 +1842,7 @@ class MaxTicketsModal(discord.ui.Modal): ) self.add_item(self.max_tickets) - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): try: max_tickets = int(self.max_tickets.value) if max_tickets < 1 or max_tickets > 50: @@ -1852,10 +1852,10 @@ class MaxTicketsModal(discord.ui.Modal): self.config.max_tickets_per_user = max_tickets self.storage.save_config(interaction.guild_id, self.config) - embed = discord.Embed( + embed = disnake.Embed( title="✅ Limite Mise à Jour", description=f"La limite de tickets par utilisateur est maintenant de **{max_tickets}**.", - color=discord.Color.green() + color=disnake.Color.green() ) await interaction.response.send_message(embed=embed, ephemeral=True) @@ -1863,7 +1863,7 @@ class MaxTicketsModal(discord.ui.Modal): await interaction.response.send_message("Veuillez entrer un nombre valide.", ephemeral=True) -class ConfigPanelView(discord.ui.View): +class ConfigPanelView(disnake.ui.View): """Panel de configuration""" def __init__(self, bot, storage, config: GuildTicketConfig): @@ -1873,8 +1873,8 @@ class ConfigPanelView(discord.ui.View): self.config = config # Toggle enabled - toggle_btn = discord.ui.Button( - style=discord.ButtonStyle.green if config.enabled else discord.ButtonStyle.red, + toggle_btn = disnake.ui.Button( + style=disnake.ButtonStyle.green if config.enabled else disnake.ButtonStyle.red, label="Activer/Désactiver", custom_id="config_toggle" ) @@ -1882,17 +1882,17 @@ class ConfigPanelView(discord.ui.View): self.add_item(toggle_btn) # Default category - self.category_btn = discord.ui.Button( + self.category_btn = disnake.ui.Button( label="📁 Catégorie par défaut", - style=discord.ButtonStyle.secondary, + style=disnake.ButtonStyle.secondary, custom_id="config_category" ) self.category_btn.callback = self._category_callback self.add_item(self.category_btn) # Gérer les catégories - self.manage_cat_btn = discord.ui.Button( - style=discord.ButtonStyle.blurple, + self.manage_cat_btn = disnake.ui.Button( + style=disnake.ButtonStyle.blurple, label="📂 Gérer les catégories", custom_id="config_manage_cat" ) @@ -1900,8 +1900,8 @@ class ConfigPanelView(discord.ui.View): self.add_item(self.manage_cat_btn) # Staff permissions - staff_btn = discord.ui.Button( - style=discord.ButtonStyle.secondary, + staff_btn = disnake.ui.Button( + style=disnake.ButtonStyle.secondary, label="Permissions Staff", custom_id="config_staff_perms" ) @@ -1909,8 +1909,8 @@ class ConfigPanelView(discord.ui.View): self.add_item(staff_btn) # Config permissions - config_perms_btn = discord.ui.Button( - style=discord.ButtonStyle.secondary, + config_perms_btn = disnake.ui.Button( + style=disnake.ButtonStyle.secondary, label="Permissions Config", custom_id="config_config_perms" ) @@ -1918,15 +1918,15 @@ class ConfigPanelView(discord.ui.View): self.add_item(config_perms_btn) # Log channel - log_btn = discord.ui.Button( - style=discord.ButtonStyle.secondary, + log_btn = disnake.ui.Button( + style=disnake.ButtonStyle.secondary, label="Canal de Logs", custom_id="config_log_channel" ) log_btn.callback = self._log_channel_callback self.add_item(log_btn) - async def _toggle_callback(self, interaction: discord.Interaction): + async def _toggle_callback(self, interaction: disnake.Interaction): self.config.enabled = not self.config.enabled self.storage.save_config(interaction.guild_id, self.config) @@ -1936,32 +1936,32 @@ class ConfigPanelView(discord.ui.View): # Update button for item in self.children: if item.custom_id == "config_toggle": - item.style = discord.ButtonStyle.green if self.config.enabled else discord.ButtonStyle.red + item.style = disnake.ButtonStyle.green if self.config.enabled else disnake.ButtonStyle.red await interaction.message.edit(view=self) - async def _manage_cat_callback(self, interaction: discord.Interaction): + async def _manage_cat_callback(self, interaction: disnake.Interaction): view = CategoryManagerView(self.bot, self.storage, self.config) await interaction.response.edit_message(embed=view._get_embed(), view=view) - async def _staff_perms_callback(self, interaction: discord.Interaction): + async def _staff_perms_callback(self, interaction: disnake.Interaction): view = PermissionConfigView(self.bot, self.storage, self.config, interaction.guild, "staff") - embed = discord.Embed( + embed = disnake.Embed( title="Permissions Staff", description="Configurez qui peut gérer les tickets (répondre, fermer, etc.)", - color=discord.Color.blue() + color=disnake.Color.blue() ) self._add_current_perms_fields(embed, self.config.staff_roles, self.config.staff_users, interaction.guild) await interaction.response.send_message(embed=embed, view=view, ephemeral=True) - async def _config_perms_callback(self, interaction: discord.Interaction): + async def _config_perms_callback(self, interaction: disnake.Interaction): view = PermissionConfigView(self.bot, self.storage, self.config, interaction.guild, "config") - embed = discord.Embed( + embed = disnake.Embed( title="Permissions Configuration", description="Configurez qui peut modifier les paramètres du bot (/ticketconfig)", - color=discord.Color.orange() + color=disnake.Color.orange() ) self._add_current_perms_fields(embed, self.config.config_roles, self.config.config_users, interaction.guild) @@ -1985,16 +1985,16 @@ class ConfigPanelView(discord.ui.View): embed.add_field(name="Rôles", value=", ".join(role_mentions) if role_mentions else "Aucun", inline=False) embed.add_field(name="Utilisateurs", value=", ".join(user_mentions) if user_mentions else "Aucun", inline=False) - async def _category_callback(self, interaction: discord.Interaction): + async def _category_callback(self, interaction: disnake.Interaction): modal = SetCategoryModal(self.bot, self.storage, self.config) await interaction.response.send_modal(modal) - async def _log_channel_callback(self, interaction: discord.Interaction): + async def _log_channel_callback(self, interaction: disnake.Interaction): modal = SetChannelModal(self.bot, self.storage, self.config, "log") await interaction.response.send_modal(modal) -class AddCategoryModal(discord.ui.Modal): +class AddCategoryModal(disnake.ui.Modal): """Modal pour ajouter une catégorie""" def __init__(self, bot, storage, config: GuildTicketConfig): @@ -2003,18 +2003,18 @@ class AddCategoryModal(discord.ui.Modal): self.storage = storage self.config = config - self.name = discord.ui.TextInput(label="Nom", placeholder="Ex: Support", required=True) + self.name = disnake.ui.TextInput(label="Nom", placeholder="Ex: Support", required=True) self.add_item(self.name) - self.description = discord.ui.TextInput( - label="Description", placeholder="Description...", required=False, style=discord.TextStyle.paragraph + self.description = disnake.ui.TextInput( + label="Description", placeholder="Description...", required=False, style=disnake.TextStyle.paragraph ) self.add_item(self.description) - self.emoji = discord.ui.TextInput(label="Emoji", placeholder="🎫", required=False, max_length=5) + self.emoji = disnake.ui.TextInput(label="Emoji", placeholder="🎫", required=False, max_length=5) self.add_item(self.emoji) - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): import uuid cat_id = str(uuid.uuid4())[:8] @@ -2032,7 +2032,7 @@ class AddCategoryModal(discord.ui.Modal): await interaction.response.send_message(f"Catégorie **{self.name.value}** créée!", ephemeral=True) -class EditCategoryModal(discord.ui.Modal): +class EditCategoryModal(disnake.ui.Modal): """Modal pour modifier une catégorie existante""" def __init__(self, bot, storage, config, category: TicketCategory, parent_view): @@ -2043,7 +2043,7 @@ class EditCategoryModal(discord.ui.Modal): self.category = category self.parent_view = parent_view - self.name = discord.ui.TextInput( + self.name = disnake.ui.TextInput( label="Nom", default=category.name, placeholder="Ex: Support", @@ -2051,16 +2051,16 @@ class EditCategoryModal(discord.ui.Modal): ) self.add_item(self.name) - self.description = discord.ui.TextInput( + self.description = disnake.ui.TextInput( label="Description", default=category.description, placeholder="Description...", required=False, - style=discord.TextStyle.paragraph + style=disnake.TextStyle.paragraph ) self.add_item(self.description) - self.emoji = discord.ui.TextInput( + self.emoji = disnake.ui.TextInput( label="Emoji", default=category.emoji, placeholder="🎫", @@ -2069,7 +2069,7 @@ class EditCategoryModal(discord.ui.Modal): ) self.add_item(self.emoji) - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): # Update category object self.category.name = self.name.value self.category.description = self.description.value or "" @@ -2083,7 +2083,7 @@ class EditCategoryModal(discord.ui.Modal): await interaction.followup.send(f"✅ Catégorie **{self.category.name}** mise à jour !", ephemeral=True) -class CategoryManagerView(discord.ui.View): +class CategoryManagerView(disnake.ui.View): """Vue pour lister et gérer les catégories""" def __init__(self, bot, storage, config: GuildTicketConfig): @@ -2099,14 +2099,14 @@ class CategoryManagerView(discord.ui.View): if self.config.categories: options = [] for cat in self.config.categories[:25]: # Limite Select menu - options.append(discord.SelectOption( + options.append(disnake.SelectOption( label=cat.name, value=cat.id, description=cat.description[:100] if cat.description else "Aucune description", emoji=cat.emoji )) - select = discord.ui.Select( + select = disnake.ui.Select( placeholder="Sélectionnez une catégorie à gérer...", options=options, custom_id="manage_cat_select" @@ -2115,8 +2115,8 @@ class CategoryManagerView(discord.ui.View): self.add_item(select) # Bouton Ajouter - add_btn = discord.ui.Button( - style=discord.ButtonStyle.success, + add_btn = disnake.ui.Button( + style=disnake.ButtonStyle.success, label="➕ Ajouter une catégorie", custom_id="manage_cat_add" ) @@ -2124,8 +2124,8 @@ class CategoryManagerView(discord.ui.View): self.add_item(add_btn) # Bouton Retour - back_btn = discord.ui.Button( - style=discord.ButtonStyle.secondary, + back_btn = disnake.ui.Button( + style=disnake.ButtonStyle.secondary, label="⬅️ Retour", custom_id="manage_cat_back" ) @@ -2133,11 +2133,11 @@ class CategoryManagerView(discord.ui.View): self.add_item(back_btn) def _get_embed(self): - embed = discord.Embed( + embed = disnake.Embed( title="📂 Gestion des Catégories", description=f"Il y a actuellement **{len(self.config.categories)}** catégorie(s) configurée(s).\n\n" "Sélectionnez une catégorie dans le menu pour la modifier ou la supprimer.", - color=discord.Color.blue() + color=disnake.Color.blue() ) if not self.config.categories: @@ -2152,7 +2152,7 @@ class CategoryManagerView(discord.ui.View): return embed - async def _category_select_callback(self, interaction: discord.Interaction): + async def _category_select_callback(self, interaction: disnake.Interaction): # En utilisant interaction.data.get('values') pour obtenir la valeur sélectionnée cat_id = interaction.data.get('values', [None])[0] category = self.config.get_category(cat_id) @@ -2164,14 +2164,14 @@ class CategoryManagerView(discord.ui.View): view = CategoryActionView(self.bot, self.storage, self.config, category, self) await interaction.response.edit_message(embed=view._get_embed(), view=view) - async def _add_cat_callback(self, interaction: discord.Interaction): + async def _add_cat_callback(self, interaction: disnake.Interaction): # Utilisation de la classe AddCategoryModal existante class AddCategoryManagerModal(AddCategoryModal): def __init__(self, bot, storage, config, parent_view): super().__init__(bot, storage, config) self.parent_view = parent_view - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): import uuid cat_id = str(uuid.uuid4())[:8] category = TicketCategory( @@ -2195,17 +2195,17 @@ class CategoryManagerView(discord.ui.View): modal = AddCategoryManagerModal(self.bot, self.storage, self.config, self) await interaction.response.send_modal(modal) - async def _back_callback(self, interaction: discord.Interaction): + async def _back_callback(self, interaction: disnake.Interaction): view = ConfigPanelView(self.bot, self.storage, self.config) - embed = discord.Embed( + embed = disnake.Embed( title="Configuration des Tickets", description="Gérez la configuration du système de tickets", - color=discord.Color.blue() + color=disnake.Color.blue() ) await interaction.response.edit_message(embed=embed, view=view) -class CategoryActionView(discord.ui.View): +class CategoryActionView(disnake.ui.View): """Actions pour une catégorie spécifique""" def __init__(self, bot, storage, config, category, parent_view): @@ -2217,7 +2217,7 @@ class CategoryActionView(discord.ui.View): self.parent_view = parent_view def _get_embed(self): - embed = discord.Embed( + embed = disnake.Embed( title=f"Gestion: {self.category.name}", description=f"**ID:** `{self.category.id}`\n" f"**Emoji:** {self.category.emoji}\n" @@ -2225,55 +2225,55 @@ class CategoryActionView(discord.ui.View): f"**Couleur:** `{self.category.color}`\n" f"**Staff Spécifique:** {', '.join([f'<@&{r}>' for r in self.category.staff_roles]) if self.category.staff_roles else 'Global (Tous les staffs)'}\n" f"**Catégorie Discord:** {f'<#{self.category.discord_category_id}>' if self.category.discord_category_id else 'Par défaut'}", - color=discord.Color.blue() + color=disnake.Color.blue() ) return embed - @discord.ui.button(label="📝 Modifier", style=discord.ButtonStyle.primary) - async def edit_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="📝 Modifier", style=disnake.ButtonStyle.primary) + async def edit_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): modal = EditCategoryModal(self.bot, self.storage, self.config, self.category, self) await interaction.response.send_modal(modal) - @discord.ui.button(label="🛡️ Permissions", style=discord.ButtonStyle.success) - async def permissions_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="🛡️ Permissions", style=disnake.ButtonStyle.success) + async def permissions_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): view = CategoryPermissionsView(self.bot, self.storage, self.config, self.category, self) - embed = discord.Embed( + embed = disnake.Embed( title=f"Permissions: {self.category.name}", description="Choisissez les rôles qui auront accès à cette catégorie de tickets.\n\n" "⚠️ **Si vous sélectionnez des rôles ici, seuls ces rôles (et les admins) auront accès aux tickets de cette catégorie.** " "Les rôles staff globaux n'y auront plus accès.", - color=discord.Color.green() + color=disnake.Color.green() ) await interaction.response.edit_message(embed=embed, view=view) - @discord.ui.button(label="📁 Catégorie Discord", style=discord.ButtonStyle.secondary) - async def discord_category_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="📁 Catégorie Discord", style=disnake.ButtonStyle.secondary) + async def discord_category_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): view = CategoryDiscordCategoryView(self.bot, self.storage, self.config, self.category, self) - embed = discord.Embed( + embed = disnake.Embed( title=f"Catégorie Discord: {self.category.name}", description="Choisissez la catégorie Discord (dossier) dans laquelle les tickets de ce type seront créés.\n\n" "Si aucune n'est sélectionnée, la catégorie par défaut du serveur sera utilisée.", - color=discord.Color.blue() + color=disnake.Color.blue() ) await interaction.response.edit_message(embed=embed, view=view) - @discord.ui.button(label="🗑️ Supprimer", style=discord.ButtonStyle.danger) - async def delete_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="🗑️ Supprimer", style=disnake.ButtonStyle.danger) + async def delete_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): view = DeleteCategoryConfirmationView(self.bot, self.storage, self.config, self.category, self.parent_view) - embed = discord.Embed( + embed = disnake.Embed( title="⚠️ Confirmer la suppression", description=f"Êtes-vous sûr de vouloir supprimer la catégorie **{self.category.name}** ?\n" "Cette action est irréversible.", - color=discord.Color.red() + color=disnake.Color.red() ) await interaction.response.edit_message(embed=embed, view=view) - @discord.ui.button(label="⬅️ Retour", style=discord.ButtonStyle.secondary) - async def back_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary) + async def back_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): self.parent_view._add_category_select() await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) -class CategoryPermissionsView(discord.ui.View): +class CategoryPermissionsView(disnake.ui.View): """Vue pour configurer les rôles staff d'une catégorie""" def __init__(self, bot, storage, config, category, parent_view): @@ -2284,8 +2284,8 @@ class CategoryPermissionsView(discord.ui.View): self.category = category self.parent_view = parent_view - @discord.ui.select(cls=discord.ui.RoleSelect, placeholder="Sélectionner les rôles staff (Gestion)", min_values=0, max_values=10) - async def role_select_callback(self, interaction: discord.Interaction, select: discord.ui.RoleSelect): + @disnake.ui.select(cls=disnake.ui.RoleSelect, placeholder="Sélectionner les rôles staff (Gestion)", min_values=0, max_values=10) + async def role_select_callback(self, interaction: disnake.Interaction, select: disnake.ui.RoleSelect): role_ids = [str(role.id) for role in select.values] self.category.staff_roles = role_ids self.storage.save_config(interaction.guild_id, self.config) @@ -2293,11 +2293,11 @@ class CategoryPermissionsView(discord.ui.View): # Refresh embed await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) - @discord.ui.button(label="⬅️ Annuler", style=discord.ButtonStyle.secondary) - async def back_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="⬅️ Annuler", style=disnake.ButtonStyle.secondary) + async def back_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) -class CategoryDiscordCategoryView(discord.ui.View): +class CategoryDiscordCategoryView(disnake.ui.View): """Vue pour configurer la catégorie Discord d'une catégorie de ticket""" def __init__(self, bot, storage, config, category, parent_view): @@ -2308,24 +2308,24 @@ class CategoryDiscordCategoryView(discord.ui.View): self.category = category self.parent_view = parent_view - @discord.ui.select(cls=discord.ui.ChannelSelect, channel_types=[discord.ChannelType.category], placeholder="Sélectionner la catégorie Discord") - async def select_callback(self, interaction: discord.Interaction, select: discord.ui.ChannelSelect): + @disnake.ui.select(cls=disnake.ui.ChannelSelect, channel_types=[disnake.ChannelType.category], placeholder="Sélectionner la catégorie Discord") + async def select_callback(self, interaction: disnake.Interaction, select: disnake.ui.ChannelSelect): self.category.discord_category_id = select.values[0].id self.storage.save_config(interaction.guild_id, self.config) await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) - @discord.ui.button(label="❌ Réinitialiser", style=discord.ButtonStyle.danger) - async def reset_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="❌ Réinitialiser", style=disnake.ButtonStyle.danger) + async def reset_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): self.category.discord_category_id = None self.storage.save_config(interaction.guild_id, self.config) await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) - @discord.ui.button(label="⬅️ Retour", style=discord.ButtonStyle.secondary) - async def back_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary) + async def back_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) -class DeleteCategoryConfirmationView(discord.ui.View): +class DeleteCategoryConfirmationView(disnake.ui.View): def __init__(self, bot, storage, config, category, parent_view): super().__init__(timeout=None) self.bot = bot @@ -2334,8 +2334,8 @@ class DeleteCategoryConfirmationView(discord.ui.View): self.category = category self.parent_view = parent_view - @discord.ui.button(label="✅ Confirmer", style=discord.ButtonStyle.danger) - async def confirm_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="✅ Confirmer", style=disnake.ButtonStyle.danger) + async def confirm_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): if self.category in self.config.categories: self.config.categories.remove(self.category) self.storage.save_config(interaction.guild_id, self.config) @@ -2347,13 +2347,13 @@ class DeleteCategoryConfirmationView(discord.ui.View): ) await interaction.followup.send(f"✅ Catégorie **{self.category.name}** supprimée.", ephemeral=True) - @discord.ui.button(label="❌ Annuler", style=discord.ButtonStyle.secondary) - async def cancel_callback(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="❌ Annuler", style=disnake.ButtonStyle.secondary) + async def cancel_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): view = CategoryActionView(self.bot, self.storage, self.config, self.category, self.parent_view) await interaction.response.edit_message(embed=view._get_embed(), view=view) -class SetChannelModal(discord.ui.Modal): +class SetChannelModal(disnake.ui.Modal): """Modal pour définir le canal de logs""" def __init__(self, bot, storage, config: GuildTicketConfig, channel_type: str = "log"): @@ -2363,7 +2363,7 @@ class SetChannelModal(discord.ui.Modal): self.config = config self.channel_type = channel_type - self.channel = discord.ui.TextInput( + self.channel = disnake.ui.TextInput( label="Canal de logs", placeholder="Mentionnez le canal ou entrez l'ID (ex: #general ou 123456789)", required=True, @@ -2371,7 +2371,7 @@ class SetChannelModal(discord.ui.Modal): ) self.add_item(self.channel) - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): channel_input = self.channel.value.strip() # Try to extract channel ID from mention or ID @@ -2410,15 +2410,15 @@ class SetChannelModal(discord.ui.Modal): self.config.log_channel_id = channel_id self.storage.save_config(interaction.guild_id, self.config) - embed = discord.Embed( + embed = disnake.Embed( title="✅ Canal de Logs Mis à Jour", description=f"Le canal de logs a été défini sur {channel.mention}.", - color=discord.Color.green() + color=disnake.Color.green() ) await interaction.response.send_message(embed=embed, ephemeral=True) -class StaffRolesModal(discord.ui.Modal): +class StaffRolesModal(disnake.ui.Modal): """Modal pour ajouter/supprimer les rôles staff""" def __init__(self, bot, storage, config: GuildTicketConfig): @@ -2428,7 +2428,7 @@ class StaffRolesModal(discord.ui.Modal): self.config = config # Champ pour entrer les rôles (mention ou ID) - self.roles = discord.ui.TextInput( + self.roles = disnake.ui.TextInput( label="Rôles Staff (mention ou ID)", placeholder="@Staff @Modérateurs ou 123456789 987654321", required=True, @@ -2436,7 +2436,7 @@ class StaffRolesModal(discord.ui.Modal): ) self.add_item(self.roles) - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): try: role_input = self.roles.value.strip() role_ids = [] @@ -2475,10 +2475,10 @@ class StaffRolesModal(discord.ui.Modal): self.storage.save_config(interaction.guild_id, self.config) # Build success embed - embed = discord.Embed( + embed = disnake.Embed( title="✅ Rôles Staff Mis à Jour", description=f"**{len(valid_roles)}** rôle(s) configuré(s) comme staff.", - color=discord.Color.green() + color=disnake.Color.green() ) role_mentions = [role.mention for role in valid_roles] @@ -2498,15 +2498,15 @@ class StaffRolesModal(discord.ui.Modal): except Exception as e: await interaction.response.send_message(f"Erreur lors de la configuration des rôles: {e}", ephemeral=True) - async def on_error(self, interaction: discord.Interaction, error: Exception): + async def on_error(self, interaction: disnake.Interaction, error: Exception): """Handle errors in the modal""" await interaction.response.send_message("Une erreur inattendue s'est produite lors de la configuration des rôles.", ephemeral=True) -class PermissionConfigView(discord.ui.View): +class PermissionConfigView(disnake.ui.View): """View to configure permissions (Staff/Config Roles & Users)""" - def __init__(self, bot, storage, config: GuildTicketConfig, guild: discord.Guild, config_type: str = "staff"): + def __init__(self, bot, storage, config: GuildTicketConfig, guild: disnake.Guild, config_type: str = "staff"): super().__init__(timeout=None) self.bot = bot self.storage = storage @@ -2520,7 +2520,7 @@ class PermissionConfigView(discord.ui.View): self._add_remove_select() def _add_role_select(self): - select = discord.ui.RoleSelect( + select = disnake.ui.RoleSelect( placeholder=f"Sélectionnez les rôles {self.config_type}", min_values=0, max_values=25, @@ -2530,7 +2530,7 @@ class PermissionConfigView(discord.ui.View): self.add_item(select) def _add_user_select(self): - select = discord.ui.UserSelect( + select = disnake.ui.UserSelect( placeholder=f"Sélectionnez les utilisateurs {self.config_type}", min_values=0, max_values=25, @@ -2539,7 +2539,7 @@ class PermissionConfigView(discord.ui.View): select.callback = self._user_callback self.add_item(select) - async def _role_callback(self, interaction: discord.Interaction): + async def _role_callback(self, interaction: disnake.Interaction): try: # Determine which list to update if self.config_type == "staff": @@ -2568,7 +2568,7 @@ class PermissionConfigView(discord.ui.View): traceback.print_exc() await interaction.response.send_message("Une erreur est survenue lors de la mise à jour des rôles.", ephemeral=True) - async def _user_callback(self, interaction: discord.Interaction): + async def _user_callback(self, interaction: disnake.Interaction): try: if self.config_type == "staff": target_list = self.config.staff_users @@ -2605,17 +2605,17 @@ class PermissionConfigView(discord.ui.View): if self.config_type == "staff": title = "Permissions Staff" description = "Configurez qui peut gérer les tickets (répondre, fermer, etc.)" - color = discord.Color.blue() + color = disnake.Color.blue() target_roles = self.config.staff_roles target_users = self.config.staff_users else: title = "Permissions Configuration" description = "Configurez qui peut modifier les paramètres du bot (/ticketconfig)" - color = discord.Color.orange() + color = disnake.Color.orange() target_roles = self.config.config_roles target_users = self.config.config_users - embed = discord.Embed(title=title, description=description, color=color) + embed = disnake.Embed(title=title, description=description, color=color) # Add fields logic (inline reuse) role_mentions = [] @@ -2656,7 +2656,7 @@ class PermissionConfigView(discord.ui.View): if count >= 25: break role = self.guild.get_role(int(r_id)) name = role.name if role else f"Role {r_id}" - options.append(discord.SelectOption( + options.append(disnake.SelectOption( label=f"Role: {name[:90]}", value=f"r_{r_id}", emoji="🛡️", @@ -2668,7 +2668,7 @@ class PermissionConfigView(discord.ui.View): if count >= 25: break user = self.guild.get_member(u_id) name = user.display_name if user else f"User {u_id}" - options.append(discord.SelectOption( + options.append(disnake.SelectOption( label=f"User: {name[:90]}", value=f"u_{u_id}", emoji="👤", @@ -2677,7 +2677,7 @@ class PermissionConfigView(discord.ui.View): count += 1 if options: - select = discord.ui.Select( + select = disnake.ui.Select( placeholder="Sélectionnez pour RETIRER des permissions...", options=options, min_values=1, @@ -2688,7 +2688,7 @@ class PermissionConfigView(discord.ui.View): select.callback = self._remove_callback self.add_item(select) - async def _remove_callback(self, interaction: discord.Interaction): + async def _remove_callback(self, interaction: disnake.Interaction): try: values = interaction.data.get('values', []) @@ -2759,70 +2759,70 @@ class TicketCommands(commands.Cog): # === COMMANDES === - @commands.hybrid_command(name="ticket", aliases=["tickets"]) - async def ticket_main(self, ctx): + @commands.slash_command(name="ticket", description="Ouvre le panel des tickets") + async def ticket_main(self, inter: disnake.ApplicationCommandInteraction): """Ouvre le panel des tickets""" - await ctx.defer(ephemeral=True) + await inter.response.defer(ephemeral=True) - config = self.storage.load_config(ctx.guild.id) + config = self.storage.load_config(inter.guild.id) - embed = discord.Embed( + embed = disnake.Embed( title="Système de Tickets", description="Gérez vos tickets simplement", - color=discord.Color.blurple() + color=disnake.Color.blurple() ) if not config.enabled: embed.add_field(name="État", value="Le système est actuellement **désactivé**.", inline=False) else: embed.add_field(name="Catégories", value=str(len(config.categories)), inline=True) - embed.add_field(name="Total tickets", value=str(len(self.storage.get_guild_tickets(ctx.guild.id))), inline=True) + embed.add_field(name="Total tickets", value=str(len(self.storage.get_guild_tickets(inter.guild.id))), inline=True) view = TicketPanelView(self.bot, config, self.storage) - await ctx.send(embed=embed, view=view, ephemeral=True) + await inter.edit_original_response(embed=embed, view=view) - @commands.hybrid_command(name="ticketpanel", aliases=["panneau"]) + @commands.slash_command(name="ticketpanel", description="Envoie le panel de création de tickets dans un canal") @commands.has_permissions(administrator=True) - async def ticket_panel(self, ctx, channel: discord.TextChannel = None): + async def ticket_panel(self, inter: disnake.ApplicationCommandInteraction, channel: disnake.TextChannel = None): """Envoie le panel de création de tickets dans un canal""" - await ctx.defer(ephemeral=True) + await inter.response.defer(ephemeral=True) - config = self.storage.load_config(ctx.guild.id) + config = self.storage.load_config(inter.guild.id) if not config.enabled: - await ctx.send("Le système est désactivé.", ephemeral=True) + await inter.edit_original_response(content="Le système est désactivé.") return - target = channel or ctx.channel + target = channel or inter.channel - embed = discord.Embed( + embed = disnake.Embed( title="Besoin d'aide?", description="Cliquez sur le bouton ci-dessous pour créer un ticket.\n\nNotre équipe vous répondra dès que possible!", - color=discord.Color.blurple() + color=disnake.Color.blurple() ) embed.add_field(name="Catégories disponibles", value=str(len(config.categories)), inline=False) view = TicketPanelView(self.bot, config, self.storage) await target.send(embed=embed, view=view) - await ctx.send(f"Panel envoyé dans {target.mention}", ephemeral=True) + await inter.edit_original_response(content=f"Panel envoyé dans {target.mention}") - @commands.hybrid_command(name="ticketconfig", aliases=["ticketsetup", "configticket"]) - async def ticket_config(self, ctx): + @commands.slash_command(name="ticketconfig", description="Ouvre le panel de configuration complet du système de tickets") + async def ticket_config(self, inter: disnake.ApplicationCommandInteraction): """Ouvre le panel de configuration complet du système de tickets""" - await ctx.defer(ephemeral=True) + await inter.response.defer(ephemeral=True) # Reload config - config = self.storage.load_config(ctx.guild.id) + config = self.storage.load_config(inter.guild.id) # Vérifier permissions (admin ou config roles/users) - if not can_access_config(ctx, config): - await ctx.send("❌ Cette commande est réservée aux administrateurs ou aux rôles configurés.", ephemeral=True) + if not can_access_config(inter, config): + await inter.edit_original_response(content="❌ Cette commande est réservée aux administrateurs ou aux rôles configurés.") return - embed = discord.Embed( + embed = disnake.Embed( title="⚙️ Configuration du Système de Tickets", description="Configurez complètement votre système de tickets depuis cette interface.\n\n" "**Options disponibles:**\n" @@ -2831,7 +2831,7 @@ class TicketCommands(commands.Cog): "• Gérer les catégories\n" "• Définir le canal de logs\n" "• Et bien plus...", - color=discord.Color.blue() + color=disnake.Color.blue() ) embed.add_field( @@ -2853,7 +2853,7 @@ class TicketCommands(commands.Cog): embed.set_footer(text="Utilisez les boutons ci-dessous pour configurer le système") view = ConfigPanelView(self.bot, self.storage, config) - await ctx.send(embed=embed, view=view, ephemeral=True) + await inter.edit_original_response(embed=embed, view=view) async def setup(bot): diff --git a/commandes/ticket/analytics.py b/commandes/ticket/analytics.py index 7f70d2d..2737381 100644 --- a/commandes/ticket/analytics.py +++ b/commandes/ticket/analytics.py @@ -16,9 +16,8 @@ from PIL import Image, ImageDraw, ImageFont # Add parent directory to path for imports sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) -import discord -from discord.ext import commands -from discord import app_commands +import disnake +from disnake.ext import commands from .data.storage import get_storage from .data.models import TicketData, TicketStatus, GuildTicketConfig @@ -31,7 +30,7 @@ class TicketAnalytics(commands.Cog): self.bot = bot self.storage = get_storage() - def _is_staff(self, interaction: discord.Interaction) -> bool: + def _is_staff(self, interaction: disnake.Interaction) -> bool: """Check if user is staff""" guild = interaction.guild user = interaction.user @@ -363,9 +362,8 @@ class TicketAnalytics(commands.Cog): buf.seek(0) return buf - @app_commands.command(name="ticket_analytics", description="Affiche les statistiques détaillées des tickets (Staff uniquement)") - @app_commands.describe(period="Période d'analyse (7d, 30d, 90d, all)") - async def ticket_analytics(self, interaction: discord.Interaction, period: str = "30d"): + @commands.slash_command(name="ticket_analytics", description="Affiche les statistiques détaillées des tickets (Staff uniquement)") + async def ticket_analytics(self, interaction: disnake.Interaction, period: str = commands.Param(default="30d", choices=["7d", "30d", "90d", "all"], description="Période d'analyse")): """Display comprehensive ticket analytics for staff""" await interaction.response.defer(ephemeral=True) @@ -405,10 +403,10 @@ class TicketAnalytics(commands.Cog): t.created_at >= cutoff_date for t in filtered_tickets if hasattr(t, 'response_time') and t.response_time == rt)] # Create main embed - embed = discord.Embed( + embed = disnake.Embed( title="📊 Analytics des Tickets", description=f"Statistiques pour la période: **{period}**", - color=discord.Color.blue(), + color=disnake.Color.blue(), timestamp=datetime.now() ) @@ -444,7 +442,7 @@ class TicketAnalytics(commands.Cog): rating_chart = self._create_rating_chart(stats['ratings']) await interaction.followup.send( "📊 **Distribution des Notes Clients**", - file=discord.File(rating_chart, 'rating_distribution.png'), + file=disnake.File(rating_chart, 'rating_distribution.png'), ephemeral=True ) @@ -453,7 +451,7 @@ class TicketAnalytics(commands.Cog): response_chart = self._create_response_time_chart(stats['response_times']) await interaction.followup.send( "⏱️ **Temps de Réponse**", - file=discord.File(response_chart, 'response_times.png'), + file=disnake.File(response_chart, 'response_times.png'), ephemeral=True ) @@ -462,7 +460,7 @@ class TicketAnalytics(commands.Cog): trend_chart = self._create_trend_chart(stats['tickets_by_day']) await interaction.followup.send( "📈 **Évolution des Tickets**", - file=discord.File(trend_chart, 'ticket_trends.png'), + file=disnake.File(trend_chart, 'ticket_trends.png'), ephemeral=True ) @@ -471,10 +469,10 @@ class TicketAnalytics(commands.Cog): # Send feedback summary if available if stats['feedbacks']: - feedback_embed = discord.Embed( + feedback_embed = disnake.Embed( title="💬 Commentaires Clients", description=f"Derniers commentaires ({min(5, len(stats['feedbacks']))} affichés)", - color=discord.Color.green() + color=disnake.Color.green() ) # Sort by rating (lowest first to show areas for improvement) diff --git a/commandes/ticket/feedback_view.py b/commandes/ticket/feedback_view.py index 03bcc54..fa7ce7d 100644 --- a/commandes/ticket/feedback_view.py +++ b/commandes/ticket/feedback_view.py @@ -1,7 +1,7 @@ -import discord +import disnake from .data.models import TicketData -class FeedbackView(discord.ui.View): +class FeedbackView(disnake.ui.View): def __init__(self, bot, storage, ticket: TicketData, staff_rating_only: bool = False): super().__init__(timeout=86400) # 24h timeout self.bot = bot @@ -14,12 +14,12 @@ class FeedbackView(discord.ui.View): # Add Select Menu for Rating self.add_item(RatingSelect()) - @discord.ui.button(label="📝 Laisser un commentaire", style=discord.ButtonStyle.secondary, custom_id="feedback_comment", row=1) - async def comment_button(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="📝 Laisser un commentaire", style=disnake.ButtonStyle.secondary, custom_id="feedback_comment", row=1) + async def comment_button(self, interaction: disnake.Interaction, button: disnake.ui.Button): await interaction.response.send_modal(FeedbackModal(self)) - @discord.ui.button(label="✅ Envoyer", style=discord.ButtonStyle.green, custom_id="feedback_submit", row=1) - async def submit_button(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="✅ Envoyer", style=disnake.ButtonStyle.green, custom_id="feedback_submit", row=1) + async def submit_button(self, interaction: disnake.Interaction, button: disnake.ui.Button): if self.rating == 0: await interaction.response.send_message("❌ Veuillez sélectionner une note avant d'envoyer.", ephemeral=True) return @@ -66,11 +66,11 @@ class FeedbackView(discord.ui.View): if staff_user: # Create feedback embed - embed = discord.Embed( + embed = disnake.Embed( title="📝 Nouveau Commentaire Reçu", description=f"Un utilisateur a laissé un avis sur le ticket #{self.ticket.channel_id}", - color=discord.Color.gold() if self.rating < 3 else discord.Color.green(), - timestamp=discord.utils.utcnow() + color=disnake.Color.gold() if self.rating < 3 else disnake.Color.green(), + timestamp=disnake.utils.utcnow() ) embed.add_field( @@ -99,7 +99,7 @@ class FeedbackView(discord.ui.View): try: await staff_user.send(embed=embed) print(f"DEBUG: DM sent successfully to {staff_user}", flush=True) - except discord.Forbidden: + except disnake.Forbidden: print(f"DEBUG: DM failed - Forbidden (DMs closed)", flush=True) except Exception as e: print(f"DEBUG: Error sending feedback DM: {e}", flush=True) @@ -111,34 +111,34 @@ class FeedbackView(discord.ui.View): except Exception as e: print(f"Error in feedback notification: {e}", flush=True) -class RatingSelect(discord.ui.Select): +class RatingSelect(disnake.ui.Select): def __init__(self): options = [ - discord.SelectOption(label="⭐⭐⭐⭐⭐ (Excellent)", value="5", description="Service parfait !"), - discord.SelectOption(label="⭐⭐⭐⭐ (Très bien)", value="4", description="Très bon service"), - discord.SelectOption(label="⭐⭐⭐ (Bien)", value="3", description="Correct"), - discord.SelectOption(label="⭐⭐ (Moyen)", value="2", description="Peut mieux faire"), - discord.SelectOption(label="⭐ (Mauvais)", value="1", description="Insatisfaisant") + disnake.SelectOption(label="⭐⭐⭐⭐⭐ (Excellent)", value="5", description="Service parfait !"), + disnake.SelectOption(label="⭐⭐⭐⭐ (Très bien)", value="4", description="Très bon service"), + disnake.SelectOption(label="⭐⭐⭐ (Bien)", value="3", description="Correct"), + disnake.SelectOption(label="⭐⭐ (Moyen)", value="2", description="Peut mieux faire"), + disnake.SelectOption(label="⭐ (Mauvais)", value="1", description="Insatisfaisant") ] super().__init__(placeholder="Notez le service...", min_values=1, max_values=1, options=options, row=0) - async def callback(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.Interaction): self.view.rating = int(self.values[0]) await interaction.response.defer() -class FeedbackModal(discord.ui.Modal): +class FeedbackModal(disnake.ui.Modal): def __init__(self, view): super().__init__(title="Votre avis") self.view = view - self.comment = discord.ui.TextInput( + self.comment = disnake.ui.TextInput( label="Commentaire", - style=discord.TextStyle.paragraph, + style=disnake.TextStyle.paragraph, placeholder="Dites-nous ce que vous avez pensé du support...", required=True, max_length=1000 ) self.add_item(self.comment) - async def on_submit(self, interaction: discord.Interaction): + async def on_submit(self, interaction: disnake.Interaction): self.view.comment = self.comment.value await interaction.response.send_message("Commentaire enregistré ! N'oubliez pas de valider avec 'Envoyer'.", ephemeral=True) diff --git a/commandes/ticket/staff_analytics.py b/commandes/ticket/staff_analytics.py index 2e8925b..0ed1902 100644 --- a/commandes/ticket/staff_analytics.py +++ b/commandes/ticket/staff_analytics.py @@ -8,7 +8,7 @@ from datetime import datetime, timedelta from collections import defaultdict from typing import List, Dict, Any, Optional -import discord +import disnake import matplotlib.pyplot as plt import plotly.graph_objects as go import plotly.express as px @@ -254,7 +254,7 @@ class StaffAnalytics: return buf -class StaffStatsView(discord.ui.View): +class StaffStatsView(disnake.ui.View): """View for selecting individual staff member stats""" def __init__(self, bot, staff_list: List[Dict[str, Any]], analytics: StaffAnalytics): @@ -272,7 +272,7 @@ class StaffStatsView(discord.ui.View): description = f"Note: {staff['average_rating']:.1f}⭐" if staff['total_ratings'] > 0 else "Aucune évaluation" options.append( - discord.SelectOption( + disnake.SelectOption( label=label, value=str(staff['user_id']), description=description[:50] @@ -280,14 +280,14 @@ class StaffStatsView(discord.ui.View): ) if options: - select = discord.ui.Select( + select = disnake.ui.Select( placeholder="Sélectionnez un membre du staff...", options=options ) select.callback = self._staff_selected self.add_item(select) - async def _staff_selected(self, interaction: discord.Interaction): + async def _staff_selected(self, interaction: disnake.Interaction): """Handle staff selection""" staff_id = int(interaction.data["values"][0]) staff_data = next((s for s in self.staff_list if s['user_id'] == staff_id), None) @@ -301,10 +301,10 @@ class StaffStatsView(discord.ui.View): # Create individual chart chart = self.analytics.create_individual_staff_chart(staff_data) - embed = discord.Embed( + embed = disnake.Embed( title=f"📊 Statistiques de {staff_data['name']}", description="Performances détaillées du membre du staff", - color=discord.Color.blue(), + color=disnake.Color.blue(), timestamp=datetime.now() ) @@ -331,4 +331,4 @@ class StaffStatsView(discord.ui.View): inline=True ) - await interaction.followup.send(embed=embed, file=discord.File(chart, 'staff_stats.png'), ephemeral=True) + await interaction.followup.send(embed=embed, file=disnake.File(chart, 'staff_stats.png'), ephemeral=True) diff --git a/commandes/ticket/utils/formatters.py b/commandes/ticket/utils/formatters.py index b09dd12..e8ede79 100644 --- a/commandes/ticket/utils/formatters.py +++ b/commandes/ticket/utils/formatters.py @@ -5,7 +5,7 @@ Beautiful embed and message formatters for the ticket system. """ from datetime import datetime from typing import Optional, List, Dict, Any -from discord import Embed, Color, User, Member, Role +from disnake import Embed, Color, User, Member, Role from ..data.models import ( TicketData, diff --git a/commandes/ticket/utils/permissions.py b/commandes/ticket/utils/permissions.py index 6856f93..579b08e 100644 --- a/commandes/ticket/utils/permissions.py +++ b/commandes/ticket/utils/permissions.py @@ -3,13 +3,13 @@ Ticket Permissions Utility ========================== Centralized permission logic for the ticket system. """ -import discord +import disnake from typing import TYPE_CHECKING, Optional if TYPE_CHECKING: from ..data.models import GuildTicketConfig, TicketCategory -def is_staff(interaction: discord.Interaction, config: 'GuildTicketConfig', category: Optional['TicketCategory'] = None) -> bool: +def is_staff(interaction: disnake.Interaction, config: 'GuildTicketConfig', category: Optional['TicketCategory'] = None) -> bool: """ Check if the user has staff permissions. @@ -51,7 +51,7 @@ def is_staff(interaction: discord.Interaction, config: 'GuildTicketConfig', cate return False -def can_access_config(interaction: discord.Interaction, config: 'GuildTicketConfig') -> bool: +def can_access_config(interaction: disnake.Interaction, config: 'GuildTicketConfig') -> bool: """ Check if the user can access the configuration. @@ -87,7 +87,7 @@ def can_access_config(interaction: discord.Interaction, config: 'GuildTicketConf return False -def is_whitelisted(interaction: discord.Interaction, config: 'GuildTicketConfig', bot) -> bool: +def is_whitelisted(interaction: disnake.Interaction, config: 'GuildTicketConfig', bot) -> bool: """ Check if user is whitelisted or has staff/config permissions. """ diff --git a/commandes/ticket/utils/transcript.py b/commandes/ticket/utils/transcript.py index 7c88e40..6ac5860 100644 --- a/commandes/ticket/utils/transcript.py +++ b/commandes/ticket/utils/transcript.py @@ -7,7 +7,7 @@ import json import os from datetime import datetime from typing import Optional, List, Dict, Any -from discord import User, Member +from disnake import User, Member from ..data.models import TicketData, TicketMessage diff --git a/commandes/ticket_whitelist.py b/commandes/ticket_whitelist.py index 4cb37ff..7f9589f 100644 --- a/commandes/ticket_whitelist.py +++ b/commandes/ticket_whitelist.py @@ -1,7 +1,7 @@ -import discord +import disnake import re -from discord.ext import commands -from discord import app_commands, ui +from disnake.ext import commands +from disnake import ui import json import os import asyncio @@ -63,12 +63,12 @@ def save_settings(guild_id: int, settings: dict) -> None: with open(TICKET_WHITELIST_SETTINGS_FILE, "w", encoding='utf-8') as f: json.dump(data, f, indent=4, ensure_ascii=False) -async def get_ticket_user_id(channel: discord.TextChannel) -> Optional[int]: +async def get_ticket_user_id(channel: disnake.TextChannel) -> Optional[int]: """Récupère l'ID du propriétaire du ticket de manière robuste (gère le lag de cache)""" import re topic = getattr(channel, "topic", None) - # Tentative d'utilisation du cache d.py (plus rapide) + # Tentative d'utilisation du cache (plus rapide) if topic: match = re.search(r"(\d{17,20})", topic) if match: @@ -88,12 +88,12 @@ async def get_ticket_user_id(channel: discord.TextChannel) -> Optional[int]: return None -class TicketWhitelistConfigView(ui.View): +class TicketWhitelistConfigView(disnake.ui.View): def __init__(self, guild_id: int): super().__init__(timeout=60) self.guild_id = guild_id - async def prompt_for_id(self, interaction: discord.Interaction, prompt_text: str, is_category: bool = False): + async def prompt_for_id(self, interaction: disnake.Interaction, prompt_text: str, is_category: bool = False): await interaction.response.send_message(prompt_text, ephemeral=True) def check(m): @@ -104,7 +104,7 @@ class TicketWhitelistConfigView(ui.View): target = None if is_category: if msg.content.isdigit(): - target = discord.utils.get(interaction.guild.categories, id=int(msg.content)) + target = disnake.utils.get(interaction.guild.categories, id=int(msg.content)) if not target: await interaction.followup.send("❌ Catégorie introuvable avec cet ID.", ephemeral=True) return None @@ -129,8 +129,8 @@ class TicketWhitelistConfigView(ui.View): await interaction.followup.send("❌ Temps écoulé.", ephemeral=True) return None - @ui.button(label="Catégorie Tickets", style=discord.ButtonStyle.primary, row=0, emoji="📁") - async def set_category(self, interaction: discord.Interaction, button: ui.Button): + @ui.button(label="Catégorie Tickets", style=disnake.ButtonStyle.primary, row=0, emoji="📁") + async def set_category(self, interaction: disnake.Interaction, button: ui.Button): category = await self.prompt_for_id(interaction, "Envoie l'ID de la **catégorie** où créer les tickets :", is_category=True) if category: settings = load_settings(self.guild_id) @@ -138,8 +138,8 @@ class TicketWhitelistConfigView(ui.View): save_settings(self.guild_id, settings) await interaction.followup.send(f"✅ Catégorie configurée sur **{category.name}**", ephemeral=True) - @ui.button(label="Rôle Staff", style=discord.ButtonStyle.primary, row=0, emoji="🛡️") - async def set_staff_role(self, interaction: discord.Interaction, button: ui.Button): + @ui.button(label="Rôle Staff", style=disnake.ButtonStyle.primary, row=0, emoji="🛡️") + async def set_staff_role(self, interaction: disnake.Interaction, button: ui.Button): role = await self.prompt_for_id(interaction, "Mentionne le **rôle Staff** ou envoie son ID :") if role: settings = load_settings(self.guild_id) @@ -147,8 +147,8 @@ class TicketWhitelistConfigView(ui.View): save_settings(self.guild_id, settings) await interaction.followup.send(f"✅ Rôle Staff configuré sur **{role.name}**", ephemeral=True) - @ui.button(label="Rôle Équipe Staff", style=discord.ButtonStyle.primary, row=0, emoji="👥") - async def set_team_staff_role(self, interaction: discord.Interaction, button: ui.Button): + @ui.button(label="Rôle Équipe Staff", style=disnake.ButtonStyle.primary, row=0, emoji="👥") + async def set_team_staff_role(self, interaction: disnake.Interaction, button: ui.Button): role = await self.prompt_for_id(interaction, "Mentionne le **rôle Équipe Staff** (à ping pour les nouveaux tickets) ou envoie son ID :") if role: settings = load_settings(self.guild_id) @@ -156,8 +156,8 @@ class TicketWhitelistConfigView(ui.View): save_settings(self.guild_id, settings) await interaction.followup.send(f"✅ Rôle Équipe Staff configuré sur **{role.name}**", ephemeral=True) - @ui.button(label="Rôle Attente Oral", style=discord.ButtonStyle.secondary, row=1, emoji="🎤") - async def set_attente_oral_role(self, interaction: discord.Interaction, button: ui.Button): + @ui.button(label="Rôle Attente Oral", style=disnake.ButtonStyle.secondary, row=1, emoji="🎤") + async def set_attente_oral_role(self, interaction: disnake.Interaction, button: ui.Button): role = await self.prompt_for_id(interaction, "Mentionne le rôle **Attente Oral** ou envoie son ID :") if role: settings = load_settings(self.guild_id) @@ -165,8 +165,8 @@ class TicketWhitelistConfigView(ui.View): save_settings(self.guild_id, settings) await interaction.followup.send(f"✅ Rôle Attente Oral configuré sur **{role.name}**", ephemeral=True) - @ui.button(label="Rôle Candidature à faire", style=discord.ButtonStyle.secondary, row=1, emoji="📝") - async def set_candidature_a_faire_role(self, interaction: discord.Interaction, button: ui.Button): + @ui.button(label="Rôle Candidature à faire", style=disnake.ButtonStyle.secondary, row=1, emoji="📝") + async def set_candidature_a_faire_role(self, interaction: disnake.Interaction, button: ui.Button): role = await self.prompt_for_id(interaction, "Mentionne le rôle **Candidature à faire** ou envoie son ID :") if role: settings = load_settings(self.guild_id) @@ -174,8 +174,8 @@ class TicketWhitelistConfigView(ui.View): save_settings(self.guild_id, settings) await interaction.followup.send(f"✅ Rôle Candidature à faire configuré sur **{role.name}**", ephemeral=True) - @ui.button(label="Rôle Citoyen", style=discord.ButtonStyle.success, row=1, emoji="🏙️") - async def set_citoyen_role(self, interaction: discord.Interaction, button: ui.Button): + @ui.button(label="Rôle Citoyen", style=disnake.ButtonStyle.success, row=1, emoji="🏙️") + async def set_citoyen_role(self, interaction: disnake.Interaction, button: ui.Button): role = await self.prompt_for_id(interaction, "Mentionne le rôle **Citoyen** ou envoie son ID :") if role: settings = load_settings(self.guild_id) @@ -184,7 +184,7 @@ class TicketWhitelistConfigView(ui.View): await interaction.followup.send(f"✅ Rôle Citoyen configuré sur **{role.name}**", ephemeral=True) -class ContinueFormView(discord.ui.View): +class ContinueFormView(disnake.ui.View): """Vue avec un bouton pour continuer à l'étape suivante""" def __init__(self, current_step, step1_data=None, step2_part1_data=None, step2_part2_data=None): @@ -194,8 +194,8 @@ class ContinueFormView(discord.ui.View): self.step2_part1_data = step2_part1_data self.step2_part2_data = step2_part2_data - @discord.ui.button(label="📋 Continuer le formulaire", style=discord.ButtonStyle.green, custom_id="continue_form") - async def continue_form(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="📋 Continuer le formulaire", style=disnake.ButtonStyle.green, custom_id="continue_form") + async def continue_form(self, interaction: disnake.Interaction, button: disnake.ui.Button): """Passe à l'étape suivante du formulaire""" if self.current_step == 1: await interaction.response.send_modal(TicketStep2Part1(self.step1_data)) @@ -207,79 +207,99 @@ class ContinueFormView(discord.ui.View): # Vérifier si le message existe toujours avant de le modifier try: await interaction.message.edit(view=self) - except discord.errors.NotFound: + except disnake.errors.NotFound: # Si le message n'existe plus, nous ne faisons rien pass -class TicketStep1(discord.ui.Modal, title="📋 Informations Personnelles"): +class TicketStep1(disnake.ui.Modal): """Étape 1 du formulaire - Infos personnelles""" - pseudo = discord.ui.TextInput(label="Pseudo Epic (Fortnite)", required=True, max_length=16) - age = discord.ui.TextInput(label="Âge", required=True, max_length=2) - experience = discord.ui.TextInput(label="Expérience RP ?", style=discord.TextStyle.paragraph, required=True, max_length=50) - decouverte = discord.ui.TextInput(label="Comment as-tu découvert le serveur ?", required=True, max_length=20) - micro = discord.ui.TextInput(label="As-tu un micro de qualité ?", required=True, max_length=3) + def __init__(self): + super().__init__( + title="📋 Informations Personnelles", + custom_id="ticket_step1", + components=[ + disnake.ui.TextInput(label="Pseudo Epic (Fortnite)", custom_id="pseudo", required=True, max_length=16), + disnake.ui.TextInput(label="Âge", custom_id="age", required=True, max_length=2), + disnake.ui.TextInput(label="Expérience RP ?", custom_id="experience", style=disnake.TextInputStyle.paragraph, required=True, max_length=50), + disnake.ui.TextInput(label="Comment as-tu découvert le serveur ?", custom_id="decouverte", required=True, max_length=20), + disnake.ui.TextInput(label="As-tu un micro de qualité ?", custom_id="micro", required=True, max_length=3), + ] + ) - async def on_submit(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.ModalInteraction): """Envoie un bouton pour passer à l'étape 2""" - view = ContinueFormView(current_step=1, step1_data=self) + view = ContinueFormView(current_step=1, step1_data=interaction.text_values) await interaction.response.send_message("✅ Première étape terminée ! Cliquez ci-dessous pour continuer.", view=view, ephemeral=True) -class TicketStep2Part1(discord.ui.Modal, title="👤 Infos sur ton personnage (Partie 1)"): +class TicketStep2Part1(disnake.ui.Modal): """Étape 2 du formulaire - Infos RP (Partie 1)""" def __init__(self, step1_data): - super().__init__() self.step1_data = step1_data + super().__init__( + title="👤 Infos sur ton personnage (Partie 1)", + custom_id="ticket_step2_part1", + components=[ + disnake.ui.TextInput(label="Prénom", custom_id="prenom", required=True), + disnake.ui.TextInput(label="Nom", custom_id="nom", required=True), + disnake.ui.TextInput(label="Âge du personnage", custom_id="age_perso", required=True), + disnake.ui.TextInput(label="Genre", custom_id="genre", required=True, max_length=5), + disnake.ui.TextInput(label="Date et lieu de naissance", custom_id="naissance", required=True), + ] + ) - prenom = discord.ui.TextInput(label="Prénom", required=True) - nom = discord.ui.TextInput(label="Nom", required=True) - age_perso = discord.ui.TextInput(label="Âge du personnage", required=True) - genre = discord.ui.TextInput(label="Genre", required=True, max_length=5) - naissance = discord.ui.TextInput(label="Date et lieu de naissance", required=True) - - async def on_submit(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.ModalInteraction): """Envoie un bouton pour passer à l'étape suivante""" - view = ContinueFormView(current_step=2, step1_data=self.step1_data, step2_part1_data=self) + view = ContinueFormView(current_step=2, step1_data=self.step1_data, step2_part1_data=interaction.text_values) await interaction.response.send_message("✅ Deuxième partie terminée ! Cliquez ci-dessous pour continuer.", view=view, ephemeral=True) -class TicketStep2Part2(discord.ui.Modal, title="👤 Infos sur ton personnage (Partie 2)"): +class TicketStep2Part2(disnake.ui.Modal): """Étape 2 du formulaire - Infos RP (Partie 2)""" def __init__(self, step1_data, step2_part1_data): - super().__init__() self.step1_data = step1_data self.step2_part1_data = step2_part1_data + super().__init__( + title="👤 Infos sur ton personnage (Partie 2)", + custom_id="ticket_step2_part2", + components=[ + disnake.ui.TextInput(label="Traits de caractère", custom_id="traits", required=True), + disnake.ui.TextInput(label="Nationalité", custom_id="nationalite", required=True), + disnake.ui.TextInput(label="Skin RP", custom_id="skin_rp", required=True), + disnake.ui.TextInput(label="Métier souhaité", custom_id="metier_souhaite", required=True), + ] + ) - traits = discord.ui.TextInput(label="Traits de caractère", required=True) - nationalite = discord.ui.TextInput(label="Nationalité", required=True) - skin_rp = discord.ui.TextInput(label="Skin RP", required=True) - metier_souhaite = discord.ui.TextInput(label="Métier souhaité", required=True) - - async def on_submit(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.ModalInteraction): """Envoie un bouton pour passer à l'étape 3""" - view = ContinueFormView(current_step=3, step1_data=self.step1_data, step2_part1_data=self.step2_part1_data, step2_part2_data=self) + view = ContinueFormView(current_step=3, step1_data=self.step1_data, step2_part1_data=self.step2_part1_data, step2_part2_data=interaction.text_values) await interaction.response.send_message("✅ Deuxième étape terminée ! Cliquez ci-dessous pour continuer.", view=view, ephemeral=True) -class TicketStep3(discord.ui.Modal, title="📋 Développement du Personnage"): +class TicketStep3(disnake.ui.Modal): """Étape 3 du formulaire - Histoire du personnage""" def __init__(self, step1_data, step2_part1_data, step2_part2_data): - super().__init__() self.step1_data = step1_data self.step2_part1_data = step2_part1_data self.step2_part2_data = step2_part2_data + super().__init__( + title="📋 Développement du Personnage", + custom_id="ticket_step3", + components=[ + disnake.ui.TextInput( + label="Histoire du personnage", + custom_id="histoire", + style=disnake.TextInputStyle.paragraph, + required=True, + min_length=425, + max_length=500 + ), + disnake.ui.TextInput(label="Objectifs RP (court et long terme)", custom_id="objectifs", style=disnake.TextInputStyle.paragraph, required=True, max_length=100), + ] + ) - histoire = discord.ui.TextInput( - label="Histoire du personnage", - style=discord.TextStyle.paragraph, - required=True, - min_length=425, - max_length=500 # Limite de caractères maximum - ) - objectifs = discord.ui.TextInput(label="Objectifs RP (court et long terme)", style=discord.TextStyle.paragraph, required=True, max_length=100) - - async def on_submit(self, interaction: discord.Interaction): + async def callback(self, interaction: disnake.ModalInteraction): """Création du ticket après validation""" guild = interaction.guild user = interaction.user @@ -292,12 +312,12 @@ class TicketStep3(discord.ui.Modal, title="📋 Développement du Personnage"): if not category_id or not staff_role_id or not team_staff_role_id: return await interaction.response.send_message("❌ Erreur : Le système de tickets n'est pas entièrement configuré sur ce serveur (Catégorie, Rôle Staff ou Rôle Équipe Staff manquant).", ephemeral=True) - category = discord.utils.get(guild.categories, id=int(category_id)) + category = disnake.utils.get(guild.categories, id=int(category_id)) if category is None: return await interaction.response.send_message("❌ Erreur : La catégorie des tickets configurée n'existe plus !", ephemeral=True) - staff_role = discord.utils.get(guild.roles, id=int(staff_role_id)) - team_staff_role = discord.utils.get(guild.roles, id=int(team_staff_role_id)) + staff_role = disnake.utils.get(guild.roles, id=int(staff_role_id)) + team_staff_role = disnake.utils.get(guild.roles, id=int(team_staff_role_id)) if staff_role is None or team_staff_role is None: return await interaction.response.send_message("❌ Erreur : L'un des rôles staff configurés n'existe plus !", ephemeral=True) @@ -309,10 +329,10 @@ class TicketStep3(discord.ui.Modal, title="📋 Développement du Personnage"): # Définir les permissions du salon du ticket overwrites = { - guild.default_role: discord.PermissionOverwrite(read_messages=False), - user: discord.PermissionOverwrite(read_messages=True, send_messages=True), - staff_role: discord.PermissionOverwrite(read_messages=True, send_messages=True), - team_staff_role: discord.PermissionOverwrite(read_messages=True, send_messages=False) + guild.default_role: disnake.PermissionOverwrite(read_messages=False), + user: disnake.PermissionOverwrite(read_messages=True, send_messages=True), + staff_role: disnake.PermissionOverwrite(read_messages=True, send_messages=True), + team_staff_role: disnake.PermissionOverwrite(read_messages=True, send_messages=False) } # Créer un salon de ticket avec le nom [pseudo]-écrit @@ -323,28 +343,35 @@ class TicketStep3(discord.ui.Modal, title="📋 Développement du Personnage"): ) # Création de l'embed avec les infos du formulaire - embed = discord.Embed(title="📩 Ticket Whitelist", color=0x00ff00) + embed = disnake.Embed(title="📩 Ticket Whitelist", color=0x00ff00) + + # Helper to get value from data + s1 = self.step1_data + s2p1 = self.step2_part1_data + s2p2 = self.step2_part2_data + s3 = interaction.text_values + embed.add_field(name="📋 - Informations Personnelles", value=( - f"> - Pseudo Epic : {self.step1_data.pseudo.value}\n" - f"> - Âge : {self.step1_data.age.value}\n" - f"> - Expérience RP : {self.step1_data.experience.value}\n" - f"> - Découverte du serveur : {self.step1_data.decouverte.value}\n" - f"> - Micro de qualité : {self.step1_data.micro.value}" + f"> - Pseudo Epic : {s1['pseudo']}\n" + f"> - Âge : {s1['age']}\n" + f"> - Expérience RP : {s1['experience']}\n" + f"> - Découverte du serveur : {s1['decouverte']}\n" + f"> - Micro de qualité : {s1['micro']}" ), inline=False) embed.add_field(name="👤 - Informations RP", value=( - f"> - Prénom : {self.step2_part1_data.prenom.value}\n" - f"> - Nom : {self.step2_part1_data.nom.value}\n" - f"> - Âge du personnage : {self.step2_part1_data.age_perso.value}\n" - f"> - Genre : {self.step2_part1_data.genre.value}\n" - f"> - Naissance : {self.step2_part1_data.naissance.value}\n" - f"> - Traits de caractère : {self.step2_part2_data.traits.value}\n" - f"> - Nationalité : {self.step2_part2_data.nationalite.value}\n" - f"> - Skin RP : {self.step2_part2_data.skin_rp.value}\n" - f"> - Métier souhaité : {self.step2_part2_data.metier_souhaite.value}" + f"> - Prénom : {s2p1['prenom']}\n" + f"> - Nom : {s2p1['nom']}\n" + f"> - Âge du personnage : {s2p1['age_perso']}\n" + f"> - Genre : {s2p1['genre']}\n" + f"> - Naissance : {s2p1['naissance']}\n" + f"> - Traits de caractère : {s2p2['traits']}\n" + f"> - Nationalité : {s2p2['nationalite']}\n" + f"> - Skin RP : {s2p2['skin_rp']}\n" + f"> - Métier souhaité : {s2p2['metier_souhaite']}" ), inline=False) embed.add_field(name="📋 - Développement du Personnage", value=( - f"> - Histoire : {self.histoire.value}\n" - f"> - Objectifs : {self.objectifs.value}" + f"> - Histoire : {s3['histoire']}\n" + f"> - Objectifs : {s3['objectifs']}" ), inline=False) # Envoyer l'embed avec les boutons d'actions pour les staff @@ -355,25 +382,25 @@ class TicketStep3(discord.ui.Modal, title="📋 Développement du Personnage"): # Mettre à jour le pseudo du joueur si le bot a les permissions nécessaires if guild.me.guild_permissions.manage_nicknames: - new_nickname = f"{self.step2_part1_data.prenom.value} {self.step2_part1_data.nom.value} | {self.step1_data.pseudo.value}" + new_nickname = f"{s2p1['prenom']} {s2p1['nom']} | {s1['pseudo']}" if len(new_nickname) > 32: new_nickname = new_nickname[:32] try: await user.edit(nick=new_nickname) - except discord.Forbidden: pass + except disnake.Forbidden: pass # Envoyer un message de confirmation try: await interaction.response.send_message(f"✅ Ticket ouvert : {ticket_channel.mention}", ephemeral=True) - except discord.errors.NotFound: + except disnake.errors.NotFound: await interaction.followup.send(f"✅ Ticket ouvert : {ticket_channel.mention}", ephemeral=True) -class TicketManagementView(discord.ui.View): +class TicketManagementView(disnake.ui.View): """Vue pour gérer le ticket avec les boutons Fermer, Réouvrir, Claim et Supprimer""" def __init__(self): super().__init__(timeout=None) - async def get_ticket_owner(self, interaction: discord.Interaction): + async def get_ticket_owner(self, interaction: disnake.Interaction): """Récupère le propriétaire du ticket via le helper robuste""" owner_id = await get_ticket_user_id(interaction.channel) if owner_id: @@ -386,8 +413,8 @@ class TicketManagementView(discord.ui.View): return None return None - @discord.ui.button(label="🔒 Fermer le Ticket", style=discord.ButtonStyle.red, custom_id="close_ticket") - async def close_ticket(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="🔒 Fermer le Ticket", style=disnake.ButtonStyle.red, custom_id="close_ticket") + async def close_ticket(self, interaction: disnake.Interaction, button: disnake.ui.Button): """Ferme le ticket en désactivant l'envoi de messages pour le membre""" await interaction.response.defer(ephemeral=True) @@ -411,12 +438,14 @@ class TicketManagementView(discord.ui.View): new_view = ClosedTicketView() await interaction.channel.send(f"🔒 Ce ticket a été fermé par {interaction.user.mention}.", view=new_view) await interaction.followup.send("✅ Ticket fermé avec succès.", ephemeral=True) + except disnake.errors.RateLimited as e: + await interaction.followup.send(f"⚠️ Limite de débit Discord : Réessayez dans {e.retry_after:.1f}s.", ephemeral=True) except Exception as e: kuby_logger.error(f"Erreur lors de la fermeture du ticket: {e}") await interaction.followup.send(f"❌ Erreur lors de la fermeture : {e}", ephemeral=True) - @discord.ui.button(label="👤 Claim le Ticket", style=discord.ButtonStyle.blurple, custom_id="claim_ticket") - async def claim_ticket(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="👤 Claim le Ticket", style=disnake.ButtonStyle.blurple, custom_id="claim_ticket") + async def claim_ticket(self, interaction: disnake.Interaction, button: disnake.ui.Button): """Un staff prend en charge le ticket""" await interaction.response.defer(ephemeral=False) @@ -441,13 +470,13 @@ class TicketManagementView(discord.ui.View): kuby_logger.error(f"Erreur lors du claim du ticket: {e}") await interaction.followup.send(f"❌ Erreur lors du claim : {e}") -class ClosedTicketView(discord.ui.View): +class ClosedTicketView(disnake.ui.View): """Vue pour les tickets fermés avec les boutons Réouvrir et Supprimer""" def __init__(self): super().__init__(timeout=None) - async def get_ticket_owner(self, interaction: discord.Interaction): + async def get_ticket_owner(self, interaction: disnake.Interaction): """Récupère le propriétaire du ticket via le helper robuste""" owner_id = await get_ticket_user_id(interaction.channel) if owner_id: @@ -460,8 +489,8 @@ class ClosedTicketView(discord.ui.View): return None return None - @discord.ui.button(label="♻ Réouvrir le Ticket", style=discord.ButtonStyle.green, custom_id="reopen_ticket") - async def reopen_ticket(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="♻ Réouvrir le Ticket", style=disnake.ButtonStyle.green, custom_id="reopen_ticket") + async def reopen_ticket(self, interaction: disnake.Interaction, button: disnake.ui.Button): """Réouvre le ticket pour le membre""" await interaction.response.defer(ephemeral=True) @@ -478,10 +507,10 @@ class ClosedTicketView(discord.ui.View): try: # On demande au staff quel statut remettre - embed = discord.Embed( + embed = disnake.Embed( title="♻️ Réouverture du Ticket", description=f"Choisissez le statut à appliquer pour **{owner.display_name}**.", - color=discord.Color.blue() + color=disnake.Color.blue() ) view = ReopenStatusView(owner) await interaction.channel.send(embed=embed, view=view) @@ -490,25 +519,25 @@ class ClosedTicketView(discord.ui.View): kuby_logger.error(f"Erreur lors de la préparation de réouverture: {e}") await interaction.followup.send(f"❌ Erreur : {e}", ephemeral=True) -class ReopenStatusView(discord.ui.View): +class ReopenStatusView(disnake.ui.View): """Vue pour choisir le statut lors d'une réouverture""" - def __init__(self, owner: discord.Member): + def __init__(self, owner: disnake.Member): super().__init__(timeout=180) self.owner = owner - @discord.ui.button(label="Écrit (Ouvert)", style=discord.ButtonStyle.secondary) - async def set_written(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="Écrit (Ouvert)", style=disnake.ButtonStyle.secondary) + async def set_written(self, interaction: disnake.Interaction, button: disnake.ui.Button): await self.apply_status(interaction, "ouvert") - @discord.ui.button(label="Oral (À faire)", style=discord.ButtonStyle.primary) - async def set_oral(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="Oral (À faire)", style=disnake.ButtonStyle.primary) + async def set_oral(self, interaction: disnake.Interaction, button: disnake.ui.Button): await self.apply_status(interaction, "oral-à-faire") - @discord.ui.button(label="Accepté", style=discord.ButtonStyle.success) - async def set_accepted(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="Accepté", style=disnake.ButtonStyle.success) + async def set_accepted(self, interaction: disnake.Interaction, button: disnake.ui.Button): await self.apply_status(interaction, "accepté") - async def apply_status(self, interaction: discord.Interaction, prefix: str): + async def apply_status(self, interaction: disnake.Interaction, prefix: str): await interaction.response.defer(ephemeral=True) try: settings = load_settings(interaction.guild.id) @@ -555,8 +584,8 @@ class ReopenStatusView(discord.ui.View): kuby_logger.error(f"Erreur lors de la réouverture/synchro: {e}") await interaction.followup.send(f"❌ Erreur système : {e}", ephemeral=True) - @discord.ui.button(label="❌ Supprimer le Ticket", style=discord.ButtonStyle.danger, custom_id="delete_ticket") - async def delete_ticket(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="❌ Supprimer le Ticket", style=disnake.ButtonStyle.danger, custom_id="delete_ticket") + async def delete_ticket(self, interaction: disnake.Interaction, button: disnake.ui.Button): """Supprime le salon du ticket""" await interaction.response.defer(ephemeral=True) @@ -575,14 +604,14 @@ class ReopenStatusView(discord.ui.View): try: await interaction.followup.send(f"❌ Erreur lors de la suppression : {e}", ephemeral=True) except: pass -class TicketView(discord.ui.View): +class TicketView(disnake.ui.View): """Vue avec le bouton pour ouvrir le ticket""" def __init__(self): super().__init__(timeout=None) - @discord.ui.button(label="📑 Ouvrir un Ticket Whitelist", style=discord.ButtonStyle.red, custom_id="open_ticket") - async def open_ticket(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="📑 Ouvrir un Ticket Whitelist", style=disnake.ButtonStyle.red, custom_id="open_ticket") + async def open_ticket(self, interaction: disnake.Interaction, button: disnake.ui.Button): """Affiche le premier formulaire""" await interaction.response.send_modal(TicketStep1()) @@ -598,11 +627,11 @@ class Ticket(commands.Cog): self.bot.add_view(TicketManagementView()) self.bot.add_view(ClosedTicketView()) - @app_commands.command(name="ticket_whitelist", description="Envoie l'embed pour ouvrir un ticket de whitelist.") - @app_commands.checks.has_permissions(administrator=True) - async def ticket_whitelist(self, interaction: discord.Interaction): + @commands.slash_command(name="ticket_whitelist", description="Envoie l'embed pour ouvrir un ticket de whitelist.") + @commands.has_permissions(administrator=True) + async def ticket_whitelist(self, interaction: disnake.ApplicationCommandInteraction): """Envoie l'embed avec le bouton d'ouverture de ticket""" - embed = discord.Embed( + embed = disnake.Embed( title="📩 Ticket Whitelist", description="Pour réaliser votre whitelist, ouvrez un ticket et remplissez le formulaire.", color=0x00ff00 @@ -611,16 +640,16 @@ class Ticket(commands.Cog): await interaction.channel.send(embed=embed, view=view) await interaction.response.send_message("✅ Embed des tickets envoyé.", ephemeral=True) - @app_commands.command(name="ticket_whitelist_config", description="Configurer le système de tickets whitelist (Admin)") - @app_commands.checks.has_permissions(administrator=True) - async def ticket_whitelist_config(self, interaction: discord.Interaction): + @commands.slash_command(name="ticket_whitelist_config", description="Configurer le système de tickets whitelist (Admin)") + @commands.has_permissions(administrator=True) + async def ticket_whitelist_config(self, interaction: disnake.ApplicationCommandInteraction): """Interface de configuration des tickets""" settings = load_settings(interaction.guild.id) - embed = discord.Embed( + embed = disnake.Embed( title="⚙️ Configuration Tickets Whitelist", description="Utilisez les boutons ci-dessous pour lier la catégorie et les rôles.", - color=discord.Color.blue() + color=disnake.Color.blue() ) cat_id = settings.get("ticket_category_id") @@ -673,7 +702,7 @@ class Ticket(commands.Cog): user_id = user_id try: user = await message.guild.fetch_member(user_id) - except (discord.NotFound, discord.HTTPException): + except (disnake.NotFound, disnake.HTTPException): await message.channel.send("❌ Utilisateur introuvable. A-t-il quitté le serveur ?", delete_after=10) return @@ -701,19 +730,19 @@ class Ticket(commands.Cog): asyncio.create_task(message.channel.edit(name=new_name)) # Confirmation dans le salon (Feedback visuel) - embed_val = discord.Embed( + embed_val = disnake.Embed( title="🎉 Whitelist Validée !", description=f"Félicitations {user.mention}, ton entretien oral a été validé par {message.author.mention} !\nTu es désormais **Citoyen**.", - color=discord.Color.green() + color=disnake.Color.green() ) await message.channel.send(embed=embed_val) # DM au joueur (RESTAURATION DESIGN CAPTURE) try: - embed_dm = discord.Embed( + embed_dm = disnake.Embed( title="🎉 Félicitations - Citoyenneté Validée !", description=f"Nous avons le plaisir de t'annoncer que ton passage sur **{message.guild.name}** a été validé avec succès !", - color=discord.Color.green() + color=disnake.Color.green() ) embed_dm.add_field(name="🏙️ Nouveau Statut", value="Citoyen", inline=True) embed_dm.add_field(name="📍 Serveur", value=message.guild.name, inline=True) @@ -740,19 +769,19 @@ class Ticket(commands.Cog): asyncio.create_task(message.channel.edit(name=new_name)) # Confirmation dans le salon - embed_val = discord.Embed( + embed_val = disnake.Embed( title="📝 Étape Écrite Validée !", description=f"Ta candidature écrite a été validée par {message.author.mention}.\nTu passes désormais en **Attente Oral**.", - color=discord.Color.orange() + color=disnake.Color.orange() ) await message.channel.send(embed=embed_val) # DM au joueur (RESTAURATION DESIGN CAPTURE) try: - embed_dm = discord.Embed( + embed_dm = disnake.Embed( title="📝 Validation Étape Écrite", description=f"Ta candidature écrite sur **{message.guild.name}** a été examinée et validée par notre équipe !", - color=discord.Color.orange() + color=disnake.Color.orange() ) embed_dm.add_field(name="🎤 Statut Actuel", value="Attente Oral", inline=True) embed_dm.add_field(name="🛠️ Action Requise", value="Merci de te rendre dans le salon vocal 'Attente Oral' dès que tu es disponible pour passer ton entretien.", inline=False) diff --git a/commandes/website_sync.py b/commandes/website_sync.py index 4fdc55e..bb189dd 100644 --- a/commandes/website_sync.py +++ b/commandes/website_sync.py @@ -25,9 +25,8 @@ from datetime import datetime, timezone, timedelta from pathlib import Path from typing import Optional -import discord -from discord import app_commands -from discord.ext import commands, tasks +import disnake +from disnake.ext import commands, tasks from src.json_export_service import ( load_config, save_config, generate_all, @@ -81,8 +80,8 @@ class WebsiteSync(commands.Cog, name="Website Sync"): # ── Helper : embed de réponse ───────────────────────────────────────────── @staticmethod - def _embed(title: str, color: discord.Color, description: str = "") -> discord.Embed: - e = discord.Embed(title=title, color=color, timestamp=datetime.now(timezone.utc)) + def _embed(title: str, color: disnake.Color, description: str = "") -> disnake.Embed: + e = disnake.Embed(title=title, color=color, timestamp=datetime.now(timezone.utc)) if description: e.description = description return e @@ -90,31 +89,30 @@ class WebsiteSync(commands.Cog, name="Website Sync"): # ══════════════════════════════════════════════════════════════════════════ # /json-generate # ══════════════════════════════════════════════════════════════════════════ - @app_commands.command(name="json-generate", description="Génère manuellement les fichiers JSON du site web") - @app_commands.describe(cible="Quoi générer ? (défaut : tout)") - @app_commands.choices(cible=[ - app_commands.Choice(name="Tout (servers + members)", value="all"), - app_commands.Choice(name="Serveurs uniquement", value="servers"), - app_commands.Choice(name="Membres uniquement", value="members"), - ]) - @app_commands.default_permissions(administrator=True) - async def json_generate(self, interaction: discord.Interaction, cible: str = "all"): + @commands.slash_command(name="json-generate", description="Génère manuellement les fichiers JSON du site web") + @commands.has_permissions(administrator=True) + async def json_generate(self, interaction: disnake.ApplicationCommandInteraction, + cible: str = commands.Param("all", description="Quoi générer ?", choices={ + "Tout (servers + members)": "all", + "Serveurs uniquement": "servers", + "Membres uniquement": "members" + })): await interaction.response.defer(ephemeral=True) start = datetime.now() - embed = self._embed("⏳ Génération en cours...", discord.Color.yellow()) - await interaction.followup.send(embed=embed, ephemeral=True) + embed = self._embed("⏳ Génération en cours...", disnake.Color.yellow()) + await interaction.followup.send(embed=embed) try: if cible == "servers": res = await generate_servers_json(self.bot) - embed = self._embed("✅ servers.json généré", discord.Color.green()) + embed = self._embed("✅ servers.json généré", disnake.Color.green()) embed.add_field(name="Serveurs", value=str(res["count"]), inline=True) embed.add_field(name="Durée", value=f"{(datetime.now()-start).total_seconds():.1f}s", inline=True) embed.add_field(name="Fichier", value=f"`{SERVERS_FILE}`", inline=False) elif cible == "members": res = await generate_members_json(self.bot) - embed = self._embed("✅ members.json généré", discord.Color.green()) + embed = self._embed("✅ members.json généré", disnake.Color.green()) embed.add_field(name="Membres", value=str(res["count"]), inline=True) embed.add_field(name="Serveur", value=res["guildName"], inline=True) embed.add_field(name="Durée", value=f"{(datetime.now()-start).total_seconds():.1f}s", inline=True) @@ -125,7 +123,7 @@ class WebsiteSync(commands.Cog, name="Website Sync"): has_errors = bool(res["errors"]) embed = self._embed( "⚠️ Génération partielle" if has_errors else "✅ Génération complète", - discord.Color.orange() if has_errors else discord.Color.green(), + disnake.Color.orange() if has_errors else disnake.Color.green(), ) if res["servers"]: embed.add_field(name="📋 Serveurs", value=f"{res['servers']['count']} serveurs", inline=True) @@ -137,19 +135,19 @@ class WebsiteSync(commands.Cog, name="Website Sync"): embed.add_field(name="📁 Dossier", value=f"`{OUTPUT_DIR}`", inline=False) except Exception as e: - embed = self._embed("❌ Erreur", discord.Color.red(), f"```{e}```") + embed = self._embed("❌ Erreur", disnake.Color.red(), f"```{e}```") await interaction.edit_original_response(embed=embed) # ══════════════════════════════════════════════════════════════════════════ # /json-status # ══════════════════════════════════════════════════════════════════════════ - @app_commands.command(name="json-status", description="État des fichiers JSON et prochaine génération auto") - @app_commands.default_permissions(administrator=True) - async def json_status(self, interaction: discord.Interaction): + @commands.slash_command(name="json-status", description="État des fichiers JSON et prochaine génération auto") + @commands.has_permissions(administrator=True) + async def json_status(self, interaction: disnake.ApplicationCommandInteraction): await interaction.response.defer(ephemeral=True) config = load_config() - embed = self._embed("📊 Statut JSON Export", discord.Color.blurple()) + embed = self._embed("📊 Statut JSON Export", disnake.Color.blurple()) # servers.json if SERVERS_FILE.exists(): @@ -203,15 +201,14 @@ class WebsiteSync(commands.Cog, name="Website Sync"): # ══════════════════════════════════════════════════════════════════════════ # /json-config (groupe) # ══════════════════════════════════════════════════════════════════════════ - json_config_group = app_commands.Group( - name="json-config", - description="Configure la génération automatique des JSON", - default_permissions=discord.Permissions(administrator=True), - ) + @commands.slash_command(name="json-config", description="Configure la génération automatique des JSON") + @commands.has_permissions(administrator=True) + async def json_config_group(self, interaction: disnake.ApplicationCommandInteraction): + pass # ── show ────────────────────────────────────────────────────────────────── - @json_config_group.command(name="show", description="Affiche la configuration actuelle") - async def config_show(self, interaction: discord.Interaction): + @json_config_group.sub_command(name="show", description="Affiche la configuration actuelle") + async def config_show(self, interaction: disnake.ApplicationCommandInteraction): await interaction.response.defer(ephemeral=True) config = load_config() @@ -220,7 +217,7 @@ class WebsiteSync(commands.Cog, name="Website Sync"): mbr_on = ", ".join(k for k, v in config["memberFields"].items() if v) or "_Aucun_" mbr_off = ", ".join(k for k, v in config["memberFields"].items() if not v) or "_Aucun_" - embed = self._embed("⚙️ Configuration JSON Export", discord.Color.blurple()) + embed = self._embed("⚙️ Configuration JSON Export", disnake.Color.blurple()) embed.add_field(name="🕐 Génération auto", value=f"{config['autoHour']:02d}:{config['autoMinute']:02d} (heure serveur)", inline=True) embed.add_field(name="🎯 Serveur cible (members)", value=config["targetGuildId"] or "_Non défini_", inline=True) embed.add_field(name="\u200b", value="\u200b", inline=False) @@ -233,9 +230,8 @@ class WebsiteSync(commands.Cog, name="Website Sync"): await interaction.followup.send(embed=embed, ephemeral=True) # ── set-guild ──────────────────────────────────────────────────────────── - @json_config_group.command(name="set-guild", description="Définit l'ID du serveur cible pour members.json") - @app_commands.describe(guild_id="ID du serveur Discord") - async def config_set_guild(self, interaction: discord.Interaction, guild_id: str): + @json_config_group.sub_command(name="set-guild", description="Définit l'ID du serveur cible pour members.json") + async def config_set_guild(self, interaction: disnake.ApplicationCommandInteraction, guild_id: str = commands.Param(description="ID du serveur Discord")): await interaction.response.defer(ephemeral=True) try: guild = self.bot.get_guild(int(guild_id)) or await self.bot.fetch_guild(int(guild_id)) @@ -243,20 +239,21 @@ class WebsiteSync(commands.Cog, name="Website Sync"): config["targetGuildId"] = guild_id save_config(config) embed = self._embed( - "✅ Serveur cible défini", discord.Color.green(), + "✅ Serveur cible défini", disnake.Color.green(), f"**{guild.name}** (`{guild_id}`) sera utilisé pour members.json.", ) except Exception: embed = self._embed( - "❌ Serveur introuvable", discord.Color.red(), + "❌ Serveur introuvable", disnake.Color.red(), f"Le bot n'est pas dans le serveur `{guild_id}` ou l'ID est invalide.", ) await interaction.followup.send(embed=embed, ephemeral=True) # ── set-schedule ───────────────────────────────────────────────────────── - @json_config_group.command(name="set-schedule", description="Définit l'heure de génération automatique quotidienne") - @app_commands.describe(heure="Heure (0-23)", minute="Minute (0-59, défaut: 0)") - async def config_set_schedule(self, interaction: discord.Interaction, heure: int, minute: int = 0): + @json_config_group.sub_command(name="set-schedule", description="Définit l'heure de génération automatique quotidienne") + async def config_set_schedule(self, interaction: disnake.ApplicationCommandInteraction, + heure: int = commands.Param(description="Heure (0-23)"), + minute: int = commands.Param(0, description="Minute (0-59, défaut: 0)")): await interaction.response.defer(ephemeral=True) if not (0 <= heure <= 23) or not (0 <= minute <= 59): await interaction.followup.send("❌ Heure invalide.", ephemeral=True) @@ -266,42 +263,42 @@ class WebsiteSync(commands.Cog, name="Website Sync"): config["autoMinute"] = minute save_config(config) embed = self._embed( - "✅ Horaire mis à jour", discord.Color.green(), + "✅ Horaire mis à jour", disnake.Color.green(), f"Prochaine génération auto : **{heure:02d}:{minute:02d}** chaque jour.", ) - await interaction.followup.send(embed=embed, ephemeral=True) + await interaction.followup.send(embed=embed) # ── server-field ───────────────────────────────────────────────────────── - @json_config_group.command(name="server-field", description="Active/désactive un champ dans servers.json") - @app_commands.describe(champ="Champ à modifier", actif="true = inclus, false = exclu") - @app_commands.choices(champ=[app_commands.Choice(name=f, value=f) for f in SERVER_FIELDS]) - async def config_server_field(self, interaction: discord.Interaction, champ: str, actif: bool): + @json_config_group.sub_command(name="server-field", description="Active/désactive un champ dans servers.json") + async def config_server_field(self, interaction: disnake.ApplicationCommandInteraction, + champ: str = commands.Param(description="Champ à modifier", choices=SERVER_FIELDS), + actif: bool = commands.Param(description="true = inclus, false = exclu")): await interaction.response.defer(ephemeral=True) config = load_config() config["serverFields"][champ] = actif save_config(config) state = "inclus ✅" if actif else "exclu ❌" - embed = self._embed("✅ Champ servers.json mis à jour", discord.Color.green(), + embed = self._embed("✅ Champ servers.json mis à jour", disnake.Color.green(), f"`{champ}` est maintenant **{state}** de servers.json.") - await interaction.followup.send(embed=embed, ephemeral=True) + await interaction.followup.send(embed=embed) # ── member-field ───────────────────────────────────────────────────────── - @json_config_group.command(name="member-field", description="Active/désactive un champ dans members.json") - @app_commands.describe(champ="Champ à modifier", actif="true = inclus, false = exclu") - @app_commands.choices(champ=[app_commands.Choice(name=f, value=f) for f in MEMBER_FIELDS]) - async def config_member_field(self, interaction: discord.Interaction, champ: str, actif: bool): + @json_config_group.sub_command(name="member-field", description="Active/désactive un champ dans members.json") + async def config_member_field(self, interaction: disnake.ApplicationCommandInteraction, + champ: str = commands.Param(description="Champ à modifier", choices=MEMBER_FIELDS), + actif: bool = commands.Param(description="true = inclus, false = exclu")): await interaction.response.defer(ephemeral=True) config = load_config() config["memberFields"][champ] = actif save_config(config) state = "inclus ✅" if actif else "exclu ❌" - embed = self._embed("✅ Champ members.json mis à jour", discord.Color.green(), + embed = self._embed("✅ Champ members.json mis à jour", disnake.Color.green(), f"`{champ}` est maintenant **{state}** de members.json.") - await interaction.followup.send(embed=embed, ephemeral=True) + await interaction.followup.send(embed=embed) # ── reset ───────────────────────────────────────────────────────────────── - @json_config_group.command(name="reset", description="Remet la configuration par défaut") - async def config_reset(self, interaction: discord.Interaction): + @json_config_group.sub_command(name="reset", description="Remet la configuration par défaut") + async def config_reset(self, interaction: disnake.ApplicationCommandInteraction): await interaction.response.defer(ephemeral=True) from src.json_export_service import CONFIG_FILE try: @@ -309,14 +306,14 @@ class WebsiteSync(commands.Cog, name="Website Sync"): except Exception: pass load_config() # recrée depuis DEFAULT_CONFIG - embed = self._embed("✅ Configuration réinitialisée", discord.Color.green(), + embed = self._embed("✅ Configuration réinitialisée", disnake.Color.green(), "Tous les paramètres ont été remis à leurs valeurs par défaut.") - await interaction.followup.send(embed=embed, ephemeral=True) + await interaction.followup.send(embed=embed) # ── set-output-dir ──────────────────────────────────────────────────────── - @json_config_group.command(name="set-output-dir", description="Change le dossier de sortie des fichiers JSON") - @app_commands.describe(chemin="Chemin absolu ou relatif vers le dossier (ex: /var/www/site/data)") - async def config_set_output_dir(self, interaction: discord.Interaction, chemin: str): + @json_config_group.sub_command(name="set-output-dir", description="Change le dossier de sortie des fichiers JSON") + async def config_set_output_dir(self, interaction: disnake.ApplicationCommandInteraction, + chemin: str = commands.Param(description="Chemin absolu ou relatif vers le dossier (ex: /var/www/site/data)")): await interaction.response.defer(ephemeral=True) from pathlib import Path as _Path p = _Path(chemin) @@ -326,75 +323,69 @@ class WebsiteSync(commands.Cog, name="Website Sync"): config["outputDir"] = str(p) save_config(config) embed = self._embed( - "✅ Dossier de sortie mis à jour", discord.Color.green(), + "✅ Dossier de sortie mis à jour", disnake.Color.green(), f"Les JSON seront désormais écrits dans :\n`{p.resolve()}`", ) except Exception as e: - embed = self._embed("❌ Erreur", discord.Color.red(), f"Impossible d'utiliser ce chemin :\n```{e}```") - await interaction.followup.send(embed=embed, ephemeral=True) + embed = self._embed("❌ Erreur", disnake.Color.red(), f"Impossible d'utiliser ce chemin :\n```{e}```") + await interaction.followup.send(embed=embed) # ── add-founder-role ────────────────────────────────────────────────────── - @json_config_group.command(name="add-founder-role", description="Ajoute un nom de rôle reconnu comme 'fondateur'") - @app_commands.describe(nom="Nom du rôle (insensible à la casse, correspondance partielle)") - async def config_add_founder_role(self, interaction: discord.Interaction, nom: str): + @json_config_group.sub_command(name="add-founder-role", description="Ajoute un nom de rôle reconnu comme 'fondateur'") + async def config_add_founder_role(self, interaction: disnake.ApplicationCommandInteraction, + nom: str = commands.Param(description="Nom du rôle (insensible à la casse, correspondance partielle)")): await interaction.response.defer(ephemeral=True) config = load_config() roles = config.get("founderRoles", []) nom_l = nom.lower().strip() if nom_l in [r.lower() for r in roles]: - embed = self._embed("ℹ️ Déjà présent", discord.Color.yellow(), + embed = self._embed("ℹ️ Déjà présent", disnake.Color.yellow(), f"`{nom}` est déjà dans la liste des rôles fondateurs.") else: roles.append(nom_l) config["founderRoles"] = roles save_config(config) embed = self._embed( - "✅ Rôle fondateur ajouté", discord.Color.green(), + "✅ Rôle fondateur ajouté", disnake.Color.green(), f"`{nom_l}` ajouté. Liste actuelle : {', '.join(f'`{r}`' for r in roles)}", ) - await interaction.followup.send(embed=embed, ephemeral=True) + await interaction.followup.send(embed=embed) # ── remove-founder-role ─────────────────────────────────────────────────── - @json_config_group.command(name="remove-founder-role", description="Retire un nom de rôle fondateur") - @app_commands.describe(nom="Nom du rôle à retirer") - async def config_remove_founder_role(self, interaction: discord.Interaction, nom: str): + @json_config_group.sub_command(name="remove-founder-role", description="Retire un nom de rôle fondateur") + async def config_remove_founder_role(self, interaction: disnake.ApplicationCommandInteraction, + nom: str = commands.Param(description="Nom du rôle à retirer")): await interaction.response.defer(ephemeral=True) config = load_config() roles = config.get("founderRoles", []) nom_l = nom.lower().strip() updated = [r for r in roles if r.lower() != nom_l] if len(updated) == len(roles): - embed = self._embed("ℹ️ Introuvable", discord.Color.yellow(), + embed = self._embed("ℹ️ Introuvable", disnake.Color.yellow(), f"`{nom}` n'est pas dans la liste des rôles fondateurs.") else: config["founderRoles"] = updated save_config(config) reste = ', '.join(f'`{r}`' for r in updated) or "_Aucun_" embed = self._embed( - "✅ Rôle fondateur retiré", discord.Color.green(), + "✅ Rôle fondateur retiré", disnake.Color.green(), f"`{nom_l}` retiré. Liste actuelle : {reste}", ) - await interaction.followup.send(embed=embed, ephemeral=True) + await interaction.followup.send(embed=embed) # ══════════════════════════════════════════════════════════════════════════ # /json-preview # ══════════════════════════════════════════════════════════════════════════ - @app_commands.command(name="json-preview", description="Aperçu des données sans écrire les fichiers") - @app_commands.describe( - cible="Quoi prévisualiser ?", - limite="Nombre d'éléments à afficher (défaut: 3)", - ) - @app_commands.choices(cible=[ - app_commands.Choice(name="Serveurs", value="servers"), - app_commands.Choice(name="Membres", value="members"), - ]) - @app_commands.default_permissions(administrator=True) - async def json_preview(self, interaction: discord.Interaction, cible: str = "servers", limite: int = 3): + @commands.slash_command(name="json-preview", description="Aperçu des données sans écrire les fichiers") + @commands.has_permissions(administrator=True) + async def json_preview(self, interaction: disnake.ApplicationCommandInteraction, + cible: str = commands.Param("servers", description="Quoi prévisualiser ?", choices={"Serveurs": "servers", "Membres": "members"}), + limite: int = commands.Param(3, description="Nombre d'éléments à afficher (1-10, défaut: 3)")): await interaction.response.defer(ephemeral=True) limite = max(1, min(limite, 10)) # entre 1 et 10 try: - embed = self._embed(f"🔍 Aperçu — {cible}", discord.Color.blurple()) + embed = self._embed(f"🔍 Aperçu — {cible}", disnake.Color.blurple()) embed.set_footer(text="Aperçu uniquement — aucun fichier écrit") if cible == "servers": @@ -421,22 +412,22 @@ class WebsiteSync(commands.Cog, name="Website Sync"): ) except Exception as e: - embed = self._embed("❌ Erreur", discord.Color.red(), f"```{e}```") + embed = self._embed("❌ Erreur", disnake.Color.red(), f"```{e}```") await interaction.followup.send(embed=embed, ephemeral=True) # ══════════════════════════════════════════════════════════════════════════ # /json-history # ══════════════════════════════════════════════════════════════════════════ - @app_commands.command(name="json-history", description="Historique des dernières générations automatiques et manuelles") - @app_commands.describe(limite="Nombre d'entrées à afficher (défaut: 5, max: 10)") - @app_commands.default_permissions(administrator=True) - async def json_history(self, interaction: discord.Interaction, limite: int = 5): + @commands.slash_command(name="json-history", description="Historique des dernières générations automatiques et manuelles") + @commands.has_permissions(administrator=True) + async def json_history(self, interaction: disnake.ApplicationCommandInteraction, + limite: int = commands.Param(5, description="Nombre d'entrées à afficher (défaut: 5, max: 10)")): await interaction.response.defer(ephemeral=True) limite = max(1, min(limite, 10)) history = get_history() - embed = self._embed("📜 Historique des générations JSON", discord.Color.blurple()) + embed = self._embed("📜 Historique des générations JSON", disnake.Color.blurple()) if not history: embed.description = "_Aucune génération enregistrée pour l'instant._" @@ -465,9 +456,5 @@ class WebsiteSync(commands.Cog, name="Website Sync"): # ─── Setup ─────────────────────────────────────────────────────────────────── async def setup(bot: commands.Bot): - cog = WebsiteSync(bot) - await bot.add_cog(cog) - # Enregistre le groupe de commandes json-config s'il n'est pas déjà présent - if not bot.tree.get_command("json-config"): - bot.tree.add_command(cog.json_config_group) + await bot.add_cog(WebsiteSync(bot)) logger.info("Cog WebsiteSync chargé.") diff --git a/commandes/welcome.py b/commandes/welcome.py index 8b34a32..5f8172a 100644 --- a/commandes/welcome.py +++ b/commandes/welcome.py @@ -1,6 +1,5 @@ -import discord -from discord.ext import commands -from discord import app_commands +import disnake +from disnake.ext import commands import sqlite3 import aiohttp import io @@ -145,9 +144,9 @@ class Welcome(commands.Cog): gif_url = "https://i.giphy.com/media/v1.Y2lkPTc5MGI3NjExNXV1NW02NTk0bGF2ZHcyMGFna2F6amRrbGpobGpsMHowOW5xa2E5eCZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/ZtFuW4rsUbfrRzPauJ/giphy.gif" await member.send(f"{formatted_message}\n\n{gif_url}") kuby_logger.info(f"✅ [Welcome] DM envoyé avec succès à {member.name} (envoi groupé)") - except discord.Forbidden: + except disnake.Forbidden: kuby_logger.warning(f"🚫 [Welcome] Impossible d'envoyer le DM à {member.name} (DMs fermés)") - except discord.HTTPException as e: + except disnake.HTTPException as e: if e.code == 40003: # Rate limited (Opening DMs too fast) kuby_logger.warning(f"⏳ [Welcome] Limite de débit Discord atteinte pour les DMs : {member.name} n'a pas reçu son message.") else: @@ -177,13 +176,13 @@ class Welcome(commands.Cog): member_count=member.guild.member_count ) if welcome_channel_message else "Bienvenue dans le serveur !\nSi vous cherchez des RolePlay de qualité,\nVous êtes au bon endroit. " - embed = discord.Embed(title=f"Merci d'accueillir {member} 🤝", + embed = disnake.Embed(title=f"Merci d'accueillir {member} 🤝", description=channel_description, - color=discord.Color.dark_purple()) + color=disnake.Color.dark_purple()) embed.set_image(url="attachment://welcome.png") - await channel.send(content=member.mention, embed=embed, file=discord.File(img, filename="welcome.png")) + await channel.send(content=member.mention, embed=embed, file=disnake.File(img, filename="welcome.png")) - async def save_banner_attachment(self, attachment: discord.Attachment, guild_id: int) -> str: + async def save_banner_attachment(self, attachment: disnake.Attachment, guild_id: int) -> str: """Sauvegarde l'attachment et retourne le chemin du fichier""" base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "banners")) os.makedirs(base_dir, exist_ok=True) @@ -215,9 +214,9 @@ class Welcome(commands.Cog): return os.path.basename(banner_path) - @app_commands.command(name="setwelcome", description="Définit le salon, les messages de bienvenue et le fond de la bannière") - @app_commands.checks.has_permissions(administrator=True) - async def set_welcome(self, interaction: discord.Interaction, channel: discord.TextChannel, message_dm: str, message_salon: str, server_name: str, banner_file: discord.Attachment = None): + @commands.slash_command(name="setwelcome", description="Définit le salon, les messages de bienvenue et le fond de la bannière") + @commands.has_permissions(administrator=True) + async def set_welcome(self, interaction: disnake.ApplicationCommandInteraction, channel: disnake.TextChannel, message_dm: str, message_salon: str, server_name: str, banner_file: disnake.Attachment = None): banner_path = None # Si un fichier est uploadé, le sauvegarder @@ -237,7 +236,7 @@ class Welcome(commands.Cog): welcome_server_name=excluded.welcome_server_name, welcome_banner_background=COALESCE(excluded.welcome_banner_background, welcome_banner_background), welcome_channel_message=excluded.welcome_channel_message''', - (interaction.guild_id, channel.id, message_dm, server_name, banner_path, message_salon)) + (interaction.guild_id, channel.id, message_dm, server_name, banner_path, message_salon)) conn.commit() conn.close() diff --git a/commandes/whitelist.py b/commandes/whitelist.py index 5d7bcc2..4704660 100755 --- a/commandes/whitelist.py +++ b/commandes/whitelist.py @@ -1,6 +1,5 @@ -import discord -from discord import app_commands -from discord.ext import commands +import disnake +from disnake.ext import commands import json import os from typing import List @@ -32,95 +31,60 @@ class Whitelist(commands.Cog): self.whitelist[str(guild_id)] = users self.save_whitelist() - @commands.hybrid_group(name="whitelist", aliases=["wl"], description="Gère la whitelist du serveur") - async def whitelist_group(self, ctx): + @commands.slash_command(name="whitelist", description="Gère la whitelist du serveur") + async def whitelist_group(self, interaction: disnake.ApplicationCommandInteraction): """Groupe de commandes pour gérer la whitelist""" - if ctx.invoked_subcommand is None: - await ctx.send_help(ctx.command) + pass # ✅ Ajouter un utilisateur - @whitelist_group.command(name="ajouter", description="Ajoute un utilisateur à la whitelist") - @app_commands.describe(utilisateur="L'utilisateur à ajouter") - async def ajouter(self, ctx, utilisateur: discord.User): - interaction = ctx.interaction - guild_id = ctx.guild.id + @whitelist_group.sub_command(name="ajouter", description="Ajoute un utilisateur à la whitelist") + async def ajouter(self, interaction: disnake.ApplicationCommandInteraction, utilisateur: disnake.User): + guild_id = interaction.guild.id users = self._get_guild_whitelist(guild_id) user_id = str(utilisateur.id) - + if user_id in users: - msg = f"🔒 {utilisateur.mention} est déjà dans la whitelist." - if interaction: - await interaction.response.send_message(msg, ephemeral=True) - else: - await ctx.send(msg) - return - + return await interaction.response.send_message(f"🔒 {utilisateur.mention} est déjà dans la whitelist.", ephemeral=True) + users.append(user_id) self._set_guild_whitelist(guild_id, users) - msg = f"✅ {utilisateur.mention} a été ajouté à la whitelist." - if interaction: - await interaction.response.send_message(msg, ephemeral=True) - else: - await ctx.send(msg) + await interaction.response.send_message(f"✅ {utilisateur.mention} a été ajouté à la whitelist.", ephemeral=True) # ❌ Retirer un utilisateur - @whitelist_group.command(name="retirer", description="Retire un utilisateur de la whitelist") - @app_commands.describe(utilisateur="L'utilisateur à retirer") - async def retirer(self, ctx, utilisateur: discord.User): - interaction = ctx.interaction - guild_id = ctx.guild.id + @whitelist_group.sub_command(name="retirer", description="Retire un utilisateur de la whitelist") + async def retirer(self, interaction: disnake.ApplicationCommandInteraction, utilisateur: disnake.User): + guild_id = interaction.guild.id users = self._get_guild_whitelist(guild_id) user_id = str(utilisateur.id) - + if user_id not in users: - msg = f"⚠️ {utilisateur.mention} n'est pas dans la whitelist." - if interaction: - await interaction.response.send_message(msg, ephemeral=True) - else: - await ctx.send(msg) - return - + return await interaction.response.send_message(f"⚠️ {utilisateur.mention} n'est pas dans la whitelist.", ephemeral=True) + users.remove(user_id) self._set_guild_whitelist(guild_id, users) - msg = f"✅ {utilisateur.mention} a été retiré de la whitelist." - if interaction: - await interaction.response.send_message(msg, ephemeral=True) - else: - await ctx.send(msg) + await interaction.response.send_message(f"✅ {utilisateur.mention} a été retiré de la whitelist.", ephemeral=True) # 📋 Voir la whitelist - @whitelist_group.command(name="liste", description="Affiche les utilisateurs de la whitelist") - async def liste(self, ctx): - interaction = ctx.interaction - guild_id = ctx.guild.id + @whitelist_group.sub_command(name="liste", description="Affiche les utilisateurs de la whitelist") + async def liste(self, interaction: disnake.ApplicationCommandInteraction): + guild_id = interaction.guild.id users = self._get_guild_whitelist(guild_id) - + if not users: - msg = "📭 La whitelist est vide pour ce serveur." - if interaction: - await interaction.response.send_message(msg, ephemeral=True) - else: - await ctx.send(msg) - return - - # Defer if interaction because fetch_user might take time - if interaction: - await interaction.response.defer(ephemeral=True) - + return await interaction.response.send_message("📭 La whitelist est vide pour ce serveur.", ephemeral=True) + + await interaction.response.defer(ephemeral=True) + membres = [] for user_id in users: try: utilisateur = await self.bot.fetch_user(int(user_id)) membres.append(f"- {utilisateur.mention} ({utilisateur.name})") - except discord.NotFound: + except disnake.NotFound: membres.append(f"- <@{user_id}> (Utilisateur introuvable)") - + texte = "\n".join(membres) - msg = f"📃 **Utilisateurs whitelistés pour ce serveur :**\n{texte}" - if interaction: - await interaction.followup.send(msg, ephemeral=True) - else: - await ctx.send(msg) + await interaction.edit_original_response(content=f"📃 **Utilisateurs whitelistés pour ce serveur :**\n{texte}") def is_whitelisted(self, guild_id: int, user_id: int) -> bool: """Check if a user is whitelisted for a specific guild.""" diff --git a/commandes/whitelist_monitor.py b/commandes/whitelist_monitor.py index 53586bd..7390d02 100755 --- a/commandes/whitelist_monitor.py +++ b/commandes/whitelist_monitor.py @@ -1,6 +1,6 @@ -import discord -from discord.ext import commands -from discord.ui import View, Button +import disnake +from disnake.ext import commands +from disnake.ui import View, Button import json import os from datetime import datetime @@ -14,18 +14,18 @@ class WhitelistMonitor(commands.Cog): self.whitelist_file = "data/whitelist.json" self.persistent_views_added = False self.big_permissions = [ - discord.Permissions.administrator, - discord.Permissions.manage_guild, - discord.Permissions.manage_roles, - discord.Permissions.manage_channels, - discord.Permissions.kick_members, - discord.Permissions.ban_members, - discord.Permissions.manage_messages, - discord.Permissions.moderate_members, - discord.Permissions.manage_nicknames, - discord.Permissions.manage_emojis, - discord.Permissions.manage_webhooks, - discord.Permissions.view_audit_log + disnake.Permissions.administrator, + disnake.Permissions.manage_guild, + disnake.Permissions.manage_roles, + disnake.Permissions.manage_channels, + disnake.Permissions.kick_members, + disnake.Permissions.ban_members, + disnake.Permissions.manage_messages, + disnake.Permissions.moderate_members, + disnake.Permissions.manage_nicknames, + disnake.Permissions.manage_emojis, + disnake.Permissions.manage_webhooks, + disnake.Permissions.view_audit_log ] def load_whitelist(self) -> Dict[str, List[str]]: @@ -70,7 +70,7 @@ class WhitelistMonitor(commands.Cog): return True return False - def has_big_permissions(self, role: discord.Role) -> bool: + def has_big_permissions(self, role: disnake.Role) -> bool: """Check if role has significant permissions.""" permissions = role.permissions for perm in self.big_permissions: @@ -78,7 +78,7 @@ class WhitelistMonitor(commands.Cog): return True return False - async def send_whitelist_notification(self, member: discord.Member, removed_roles: List[discord.Role]): + async def send_whitelist_notification(self, member: disnake.Member, removed_roles: List[disnake.Role]): """Send DM notification to user about whitelist member losing roles.""" try: # Find guild administrators to notify @@ -87,10 +87,10 @@ class WhitelistMonitor(commands.Cog): return # Create embed - embed = discord.Embed( + embed = disnake.Embed( title="⚠️ Whitelist Alert", description=f"Un membre whitelisté a perdu des rôles importants dans **{member.guild.name}**.", - color=discord.Color.orange(), + color=disnake.Color.orange(), timestamp=datetime.now() ) @@ -121,7 +121,7 @@ class WhitelistMonitor(commands.Cog): for admin in admins: try: await admin.send(embed=embed, view=view) - except discord.Forbidden: + except disnake.Forbidden: continue # User has DMs disabled except Exception as e: @@ -155,7 +155,7 @@ class WhitelistMonitor(commands.Cog): print(f"[WhitelistMonitor] Error registering views: {e}") @commands.Cog.listener() - async def on_member_update(self, before: discord.Member, after: discord.Member): + async def on_member_update(self, before: disnake.Member, after: disnake.Member): """Monitor role changes for whitelist members.""" if before.roles == after.roles: return @@ -173,7 +173,7 @@ class WhitelistMonitor(commands.Cog): await self.send_whitelist_notification(after, important_removed) @commands.Cog.listener() - async def on_member_remove(self, member: discord.Member): + async def on_member_remove(self, member: disnake.Member): """Auto-remove from whitelist when member leaves server.""" if self.is_whitelisted(member.guild.id, member.id): removed = self.remove_from_whitelist(member.guild.id, member.id) @@ -184,10 +184,10 @@ class WhitelistMonitor(commands.Cog): if log_channel_id: log_channel = member.guild.get_channel(log_channel_id) if log_channel: - embed = discord.Embed( + embed = disnake.Embed( title="🗑️ Auto-Whitelist Removal", description=f"{member.mention} a été retiré automatiquement de la whitelist après avoir quitté le serveur.", - color=discord.Color.red(), + color=disnake.Color.red(), timestamp=datetime.now() ) await log_channel.send(embed=embed) @@ -214,15 +214,15 @@ class WhitelistRemovalView(View): self.user_id = user_id self.guild_id = guild_id - @discord.ui.button( + @disnake.ui.button( label="Retirer de la whitelist", - style=discord.ButtonStyle.red, + style=disnake.ButtonStyle.red, emoji="🗑️", custom_id="whitelist_remove_button" ) async def remove_whitelist( self, - interaction: discord.Interaction, + interaction: disnake.Interaction, button: Button ): """Remove user from whitelist when button is clicked.""" @@ -250,15 +250,15 @@ class WhitelistRemovalView(View): # Update button button.disabled = True button.label = "✅ Retiré" - button.style = discord.ButtonStyle.green + button.style = disnake.ButtonStyle.green await interaction.response.edit_message(view=self) # Send confirmation - embed = discord.Embed( + embed = disnake.Embed( title="✅ Retrait effectué", description=f"{user_mention} a été retiré de la whitelist.", - color=discord.Color.green() + color=disnake.Color.green() ) await interaction.followup.send(embed=embed, ephemeral=True) @@ -270,10 +270,10 @@ class WhitelistRemovalView(View): if log_channel_id: log_channel = guild.get_channel(log_channel_id) if log_channel: - embed = discord.Embed( + embed = disnake.Embed( title="🗑️ Whitelist Manual Removal", description=f"{user_mention} a été retiré manuellement de la whitelist.", - color=discord.Color.green(), + color=disnake.Color.green(), timestamp=datetime.now() ) embed.set_footer(text=f"Retiré par {interaction.user.display_name}") diff --git a/kuby.py b/kuby.py index a3b6f9b..8bb7762 100755 --- a/kuby.py +++ b/kuby.py @@ -28,10 +28,8 @@ kuby_logger.info("✅ Token Discord chargé avec succès") async def main(): kuby_logger.info("🔄 Démarrage de la connexion Discord...") try: - kuby_logger.debug("Starting async context manager") - async with bot: - kuby_logger.info("🔗 Connexion établie avec Discord...") - await bot.start(TOKEN) + kuby_logger.info("🔗 Connexion établie avec Discord...") + await bot.start(TOKEN) except KeyboardInterrupt: kuby_logger.info("🛑 Bot shutdown requested by user") except Exception as e: diff --git a/requirements.txt b/requirements.txt index 4319012..46b3c57 100755 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -discord.py>=2.3.0 +disnake>=2.11.0 python-dotenv>=1.0.0 Pillow>=10.0.0 aiohttp>=3.8.0 diff --git a/src/advanced_logger.py b/src/advanced_logger.py index e7b0558..035e379 100755 --- a/src/advanced_logger.py +++ b/src/advanced_logger.py @@ -1,4 +1,4 @@ -import discord +import disnake import json import os import asyncio @@ -18,7 +18,7 @@ class AdvancedLogger: os.makedirs(self.logs_dir, exist_ok=True) os.makedirs(self.member_logs_dir, exist_ok=True) - def log_message_event(self, message: discord.Message, event_type: str, details: Dict = None): + def log_message_event(self, message: disnake.Message, event_type: str, details: Dict = None): """Log message events (sent, edited, deleted)""" log_data = { "timestamp": datetime.now().isoformat(), @@ -37,7 +37,7 @@ class AdvancedLogger: asyncio.create_task(self._write_log_async("messages", log_data)) - def log_role_event(self, member: discord.Member, role: discord.Role, event_type: str): + def log_role_event(self, member: disnake.Member, role: disnake.Role, event_type: str): """Log role changes (added/removed)""" log_data = { "timestamp": datetime.now().isoformat(), @@ -52,7 +52,7 @@ class AdvancedLogger: asyncio.create_task(self._write_log_async("roles", log_data)) - def log_voice_event(self, member: discord.Member, channel: discord.VoiceChannel, event_type: str): + def log_voice_event(self, member: disnake.Member, channel: disnake.VoiceChannel, event_type: str): """Log voice channel events (join/leave)""" log_data = { "timestamp": datetime.now().isoformat(), @@ -66,7 +66,7 @@ class AdvancedLogger: asyncio.create_task(self._write_log_async("voice", log_data)) - def log_member_leave(self, member: discord.Member): + def log_member_leave(self, member: disnake.Member): """Log when a member leaves the server""" roles_data = { "role_ids": [role.id for role in member.roles if role.name != "@everyone"], @@ -91,7 +91,7 @@ class AdvancedLogger: # Also save to general logs asyncio.create_task(self._write_log_async("member_events", log_data)) - def log_member_join(self, member: discord.Member): + def log_member_join(self, member: disnake.Member): """Log when a member joins the server""" log_data = { "timestamp": datetime.now().isoformat(), @@ -162,12 +162,12 @@ class AdvancedLogger: except Exception as e: print(f"CRITICAL ERROR in AdvancedLogger._write_member_log_sync: {e}") - async def send_leave_notification(self, member: discord.Member, channel: discord.TextChannel): + async def send_leave_notification(self, member: disnake.Member, channel: disnake.TextChannel): """Send notification when member leaves""" - embed = discord.Embed( + embed = disnake.Embed( title="👋 Membre parti", - description=f"{member.mention} ({member.name}#{member.discriminator}) a quitté le serveur.", - color=discord.Color.red(), + description=f"{member.mention} ({member.name}) a quitté le serveur.", + color=disnake.Color.red(), timestamp=datetime.now() ) diff --git a/src/json_export_service.py b/src/json_export_service.py index bf7210d..60862b7 100644 --- a/src/json_export_service.py +++ b/src/json_export_service.py @@ -12,7 +12,7 @@ from datetime import datetime, timezone from pathlib import Path from typing import Optional -import discord +import disnake logger = logging.getLogger("kuby.json_export") @@ -160,7 +160,7 @@ def get_history() -> list: # ─── Génération servers.json ───────────────────────────────────────────────── -async def generate_servers_json(bot: discord.Client) -> dict: +async def generate_servers_json(bot: disnake.Client) -> dict: config = load_config() fields = config["serverFields"] founder_keys = [r.lower() for r in config.get("founderRoles", ["fondateur", "founder"])] @@ -249,7 +249,7 @@ async def generate_servers_json(bot: discord.Client) -> dict: # ─── Génération members.json ───────────────────────────────────────────────── -async def generate_members_json(bot: discord.Client, guild_id: Optional[int] = None) -> dict: +async def generate_members_json(bot: disnake.Client, guild_id: Optional[int] = None) -> dict: config = load_config() fields = config["memberFields"] target_id = guild_id or (int(config["targetGuildId"]) if config["targetGuildId"] else None) @@ -324,7 +324,7 @@ async def generate_members_json(bot: discord.Client, guild_id: Optional[int] = N # ─── Génération complète ───────────────────────────────────────────────────── -async def generate_all(bot: discord.Client, trigger: str = "auto") -> dict: +async def generate_all(bot: disnake.Client, trigger: str = "auto") -> dict: results = {"servers": None, "members": None, "errors": [], "trigger": trigger} try: results["servers"] = await generate_servers_json(bot) @@ -343,7 +343,7 @@ async def generate_all(bot: discord.Client, trigger: str = "auto") -> dict: # ─── Prévisualisation (sans écriture fichier) ──────────────────────────────── -async def preview_servers(bot: discord.Client, limit: int = 3) -> list: +async def preview_servers(bot: disnake.Client, limit: int = 3) -> list: """Retourne un aperçu de N serveurs sans écrire le fichier.""" config = load_config() fields = config["serverFields"] @@ -366,7 +366,7 @@ async def preview_servers(bot: discord.Client, limit: int = 3) -> list: return results -async def preview_members(bot: discord.Client, limit: int = 5) -> dict: +async def preview_members(bot: disnake.Client, limit: int = 5) -> dict: """Retourne un aperçu de N membres sans écrire le fichier.""" config = load_config() target_id = int(config["targetGuildId"]) if config["targetGuildId"] else None