diff --git a/backups/scratch/check_button_sig.py b/backups/scratch/check_button_sig.py new file mode 100644 index 0000000..2de5187 --- /dev/null +++ b/backups/scratch/check_button_sig.py @@ -0,0 +1,30 @@ +import disnake +from disnake.ui import View, Button +import asyncio + +class MyView(View): + def __init__(self): + super().__init__(timeout=None) + + @disnake.ui.button(label="Test") + async def my_btn(self, arg1, arg2): + print(f"Arg1 type: {type(arg1)}") + print(f"Arg2 type: {type(arg2)}") + +async def main(): + view = MyView() + btn = view.children[0] + print(f"Button: {btn}") + # Mock an interaction + class MockInteraction: + pass + + try: + await btn.callback(MockInteraction()) + except Exception as e: + print(f"Error during callback: {e}") + import traceback + traceback.print_exc() + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/backups/scratch/check_checks.py b/backups/scratch/check_checks.py new file mode 100644 index 0000000..febfc14 --- /dev/null +++ b/backups/scratch/check_checks.py @@ -0,0 +1,4 @@ +import disnake +import disnake.ext.commands as commands +bot = commands.Bot(command_prefix="!") +print(f"Check methods: {[m for m in dir(bot) if 'check' in m]}") diff --git a/scratch/check_db.py b/backups/scratch/check_db.py similarity index 100% rename from scratch/check_db.py rename to backups/scratch/check_db.py diff --git a/backups/scratch/check_disnake.py b/backups/scratch/check_disnake.py new file mode 100644 index 0000000..87603b2 --- /dev/null +++ b/backups/scratch/check_disnake.py @@ -0,0 +1,19 @@ +import disnake +import disnake.ext.commands as commands +import asyncio + +print(f"Disnake version: {disnake.__version__}") +bot = commands.Bot(command_prefix="!") +print(f"Bot has setup_hook: {hasattr(bot, 'setup_hook')}") + +async def test(): + print("Testing setup_hook call...") + class TestBot(commands.Bot): + async def setup_hook(self): + print("setup_hook CALLED!") + + tbot = TestBot(command_prefix="!") + # We won't start it because we don't have a token here, + # but we can check if the library expects it. + +asyncio.run(test()) diff --git a/backups/scratch/check_disnake_2.py b/backups/scratch/check_disnake_2.py new file mode 100644 index 0000000..24f8254 --- /dev/null +++ b/backups/scratch/check_disnake_2.py @@ -0,0 +1,7 @@ +import disnake +import disnake.ext.commands as commands +import asyncio + +bot = commands.Bot(command_prefix="!") +print(f"Bot methods: {[m for m in dir(bot) if 'setup' in m or 'load' in m]}") +print(f"Is context manager: {hasattr(bot, '__aenter__')}") diff --git a/backups/scratch/check_disnake_3.py b/backups/scratch/check_disnake_3.py new file mode 100644 index 0000000..58fe2d7 --- /dev/null +++ b/backups/scratch/check_disnake_3.py @@ -0,0 +1,6 @@ +import disnake +import disnake.ext.commands as commands +import inspect + +bot = commands.Bot(command_prefix="!") +print(f"load_extension is coroutine: {inspect.iscoroutinefunction(bot.load_extension)}") diff --git a/backups/scratch/check_textstyle.py b/backups/scratch/check_textstyle.py new file mode 100644 index 0000000..a31758a --- /dev/null +++ b/backups/scratch/check_textstyle.py @@ -0,0 +1,8 @@ +import disnake +print(f"disnake.TextStyle: {hasattr(disnake, 'TextStyle')}") +print(f"disnake.TextInputStyle: {hasattr(disnake, 'TextInputStyle')}") +try: + import disnake.ui + print(f"disnake.ui.TextInputStyle: {hasattr(disnake.ui, 'TextInputStyle')}") +except: + pass diff --git a/backups/scratch/fix_modal_view_mixup.py b/backups/scratch/fix_modal_view_mixup.py new file mode 100644 index 0000000..b3fc321 --- /dev/null +++ b/backups/scratch/fix_modal_view_mixup.py @@ -0,0 +1,52 @@ +import os +import re + +def fix_file(filepath): + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + + # Split by class to be surgical + classes = re.split(r'^(class\s+.*)$', content, flags=re.MULTILINE) + + new_content = [] + if len(classes) > 0: + new_content.append(classes[0]) # Header + + for i in range(1, len(classes), 2): + class_header = classes[i] + class_body = classes[i+1] if i+1 < len(classes) else "" + + # Determine if it's a View or a Modal + is_view = "disnake.ui.View" in class_header or "(disnake.ui.View)" in class_header or "super().__init__(timeout=" in class_body + is_modal = "disnake.ui.Modal" in class_header or "(disnake.ui.Modal)" in class_header or "super().__init__(title=" in class_body + + if is_view: + # Views use add_item + class_body = class_body.replace("self.append_component(", "self.add_item(") + # Ensure super().__init__ doesn't have components=[] if it's a view + class_body = class_body.replace("super().__init__(components=[],", "super().__init__(") + class_body = class_body.replace("super().__init__(components=[], ", "super().__init__(") + + if is_modal: + # Modals use append_component + class_body = class_body.replace("self.add_item(", "self.append_component(") + # Modals MUST have components=[] in super().__init__ if not already there + # (My previous sed might have added it correctly) + pass + + new_content.append(class_header) + new_content.append(class_body) + + final_content = "".join(new_content) + if final_content != content: + with open(filepath, 'w', encoding='utf-8') as f: + f.write(final_content) + return True + return False + +for root, dirs, files in os.walk('commandes'): + for file in files: + if file.endswith('.py'): + path = os.path.join(root, file) + if fix_file(path): + print(f"Fixed {path}") diff --git a/backups/scratch/fix_text_input_default.py b/backups/scratch/fix_text_input_default.py new file mode 100644 index 0000000..1572bbe --- /dev/null +++ b/backups/scratch/fix_text_input_default.py @@ -0,0 +1,36 @@ +import os +import re + +def fix_text_input_default(filepath): + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + + # Match TextInput( followed by anything including newlines, until ) + # This is a bit risky but we can refine it. + # We look for default= inside the arguments of TextInput + + def replace_default(match): + args = match.group(2) + new_args = args.replace("default=", "value=") + return f"{match.group(1)}{new_args})" + + # Regex: (disnake\.ui\.TextInput\(|ui\.TextInput\(|TextInput\() (.*? \)) + # We need to handle nested parentheses if any, but TextInput usually doesn't have them in args. + + new_content = re.sub(r'((?:disnake\.ui\.|ui\.|)TextInput\()(.*?\))', replace_default, content, flags=re.DOTALL) + + if new_content != content: + with open(filepath, 'w', encoding='utf-8') as f: + f.write(new_content) + return True + return False + +files_to_check = [ + "commandes/moderation.py", + "commandes/ticket/__init__.py" +] + +for path in files_to_check: + if os.path.exists(path): + if fix_text_input_default(path): + print(f"Fixed {path}") diff --git a/backups/scratch/inspect_modal.py b/backups/scratch/inspect_modal.py new file mode 100644 index 0000000..24d3d9a --- /dev/null +++ b/backups/scratch/inspect_modal.py @@ -0,0 +1,7 @@ +import disnake +import inspect + +try: + print(f"Modal init signature: {inspect.signature(disnake.ui.Modal.__init__)}") +except Exception as e: + print(f"Error inspecting Modal: {e}") diff --git a/backups/scratch/inspect_section.py b/backups/scratch/inspect_section.py new file mode 100644 index 0000000..c8d5613 --- /dev/null +++ b/backups/scratch/inspect_section.py @@ -0,0 +1,7 @@ +import disnake +import inspect + +try: + print(f"Section init signature: {inspect.signature(disnake.ui.Section.__init__)}") +except Exception as e: + print(f"Error inspecting Section: {e}") diff --git a/backups/scratch/test_container.py b/backups/scratch/test_container.py new file mode 100644 index 0000000..8e2d428 --- /dev/null +++ b/backups/scratch/test_container.py @@ -0,0 +1,3 @@ +import disnake +print(disnake.ui.Container) +print(disnake.ui.TextDisplay) diff --git a/backups/scratch/test_edit_message.py b/backups/scratch/test_edit_message.py new file mode 100644 index 0000000..10736dc --- /dev/null +++ b/backups/scratch/test_edit_message.py @@ -0,0 +1,17 @@ +import disnake +import asyncio + +async def main(): + class DummyResponse: + async def edit_message(self, *args, **kwargs): + print(kwargs) + + inter = type('DummyInter', (), {'response': DummyResponse()})() + container = disnake.ui.Container() + try: + await inter.response.edit_message(components=[container]) + print("Success") + except Exception as e: + print(f"Error: {e}") + +asyncio.run(main()) diff --git a/bot.py b/bot.py index 2130bfe..5e27395 100755 --- a/bot.py +++ b/bot.py @@ -1,4 +1,5 @@ import os +import sys from dotenv import load_dotenv # Charger les variables d'environnement AVANT tout le reste @@ -13,7 +14,6 @@ from src.logger import kuby_logger, log_performance import logging from src.advanced_logger import AdvancedLogger import subprocess -import sys # Récupération et validation de l'ID d'application application_id_raw = os.getenv("APPLICATION_ID") @@ -41,9 +41,6 @@ class MyBot(commands.Bot): application_id=application_id, ) kuby_logger.info("MyBot instance created successfully") - - async def setup_hook(self): - kuby_logger.info("🚀 Démarrage du bot Kuby...") # Initialize advanced logger self.advanced_logger = AdvancedLogger(self) @@ -54,8 +51,6 @@ class MyBot(commands.Bot): try: flask_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "serveur_flask.py") if os.path.exists(flask_path): - # On utilise DEVNULL car PM2 ou Nohup gèrent déjà les logs globaux - # et ça évite que le pipe se remplisse et bloque le serveur Flask self.flask_process = subprocess.Popen( [sys.executable, flask_path], stdout=subprocess.DEVNULL, @@ -68,6 +63,11 @@ class MyBot(commands.Bot): except Exception as e: kuby_logger.error(f"❌ Impossible de démarrer le serveur Flask: {e}") + + async def do_async_setup(self): + # --- INITIALIZATION LOGIC (Called before start() in kuby.py) --- + kuby_logger.info("🚀 Configuration initiale du bot Kuby...") + # Charger les extensions extensions = [ "commandes.whitelist", @@ -95,62 +95,43 @@ class MyBot(commands.Bot): for extension in extensions: try: - await self.load_extension(extension) + self.load_extension(extension) kuby_logger.info(f"✅ Extension chargée : {extension}") except Exception as e: kuby_logger.error(f"❌ Erreur lors du chargement de {extension} : {e}", exc_info=True) - # --- SYSTÈME DE WHITELIST (CORRIGÉ POUR ÉVITER LES ERREURS) --- - @self.check - async def whitelist_check(ctx): + # --- SYSTÈME DE WHITELIST (DÉDIÉ SLASH COMMANDS) --- + async def global_slash_whitelist_check(inter: disnake.ApplicationCommandInteraction): try: - # 1. Bypass pour le propriétaire (Owner) - if await self.is_owner(ctx.author): + if await self.is_owner(inter.author): + return True + command_name = inter.data.name + if command_name in ["ticketconfig", "ticketpanel", "ticket_config", "ticket_panel", "ticket_whitelist_config"]: return True - - # 1b. Bypass pour les commandes de config tickets - # Si la commande est une commande ticket et l'utilisateur a les perms ticket - if ctx.command and (ctx.command.name in ["ticketconfig", "ticketsetup", "configticket"]): - try: - storage = get_storage() - config = storage.load_config(ctx.guild.id) - if can_access_config(ctx, config): - return True - except Exception as e: - kuby_logger.error(f"Error checking ticket permissions in global whitelist: {e}") - # Fallthrough to normal whitelist check if error - - - # 2. Récupération du Cog whitelist_cog = self.get_cog("WhitelistMonitor") if not whitelist_cog: - kuby_logger.warning("WhitelistMonitor cog not found, allowing all commands") return True - - is_whitelisted = whitelist_cog.is_whitelisted(ctx.guild.id, ctx.author.id) - + is_whitelisted = whitelist_cog.is_whitelisted(inter.guild.id, inter.author.id) if not is_whitelisted: - kuby_logger.info(f"User {ctx.author} (ID: {ctx.author.id}) attempted to use command but is not whitelisted") - - # Réponse adaptée (Slash vs Message) + kuby_logger.info(f"🚫 [Check] Accès refusé : {inter.author} sur /{command_name}") msg = "❌ Vous n'êtes pas autorisé à utiliser cette commande. Veuillez demander à être ajouté à la whitelist." - if ctx.interaction: - await ctx.send(msg, ephemeral=True) - else: - await ctx.send(msg) + if not inter.response.is_done(): + await inter.response.send_message(msg, ephemeral=True) return False - return True except Exception as e: - kuby_logger.error(f"Error in whitelist check: {e}") + kuby_logger.error(f"⚠️ [Check] Erreur critique: {e}", exc_info=True) return True - # Sync slash commands - try: - # 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) + self.add_app_command_check(global_slash_whitelist_check) + + # --- LOGGING DES INTERACTIONS --- + @self.listen("on_interaction") + async def log_interaction(inter: disnake.Interaction): + if inter.type == disnake.InteractionType.application_command: + kuby_logger.info(f"⚡ Interaction reçue : /{inter.data.name} par {inter.user}") + + kuby_logger.info("✅ Configuration terminée") async def close(self): if hasattr(self, 'flask_process') and self.flask_process: @@ -163,22 +144,12 @@ class MyBot(commands.Bot): await super().close() # --- ÉVÉNEMENTS RESTAURÉS --- - async def on_ready(self): kuby_logger.info(f"🤖 Bot connecté : {self.user} (ID : {self.user.id})") kuby_logger.info(f"📊 Serveurs : {len(self.guilds)}") - - # Log intents status kuby_logger.info(f"🔍 Intents Status - Members: {self.intents.members}, Presences: {self.intents.presences}, Core: {self.intents.message_content}") - if not self.intents.members: - kuby_logger.warning("⚠️ L'intent MEMBERS est désactivé ! Les événements de join/leave et les DMs pourraient ne pas fonctionner correctement.") - - for guild in self.guilds: - kuby_logger.debug(f"Guild: {guild.name} (ID: {guild.id}) - Members: {guild.member_count}") - - total_members = sum(guild.member_count for guild in self.guilds) - kuby_logger.info(f"📊 Total members across all guilds: {total_members}") + kuby_logger.warning("⚠️ L'intent MEMBERS est désactivé !") kuby_logger.info("✅ Bot prêt et opérationnel !") async def on_guild_join(self, guild): @@ -209,7 +180,6 @@ class MyBot(commands.Bot): kuby_logger.info(f"👋 {member} left {member.guild.name}") if hasattr(self, 'advanced_logger'): self.advanced_logger.log_member_leave(member) - # Notification de départ try: from commandes.security import load_settings settings = load_settings(member.guild.id) @@ -246,7 +216,7 @@ class MyBot(commands.Bot): async def on_command_error(self, ctx, error): if isinstance(error, commands.CheckFailure): - return # Déjà géré dans le whitelist_check + return kuby_logger.error(f"Command error in {ctx.command}: {str(error)}", exc_info=True) bot = MyBot() \ No newline at end of file diff --git a/commandes/Backups_project/blacklist.py b/commandes/Backups_project/blacklist.py index e7c900b..af0b345 100755 --- a/commandes/Backups_project/blacklist.py +++ b/commandes/Backups_project/blacklist.py @@ -1,6 +1,6 @@ import discord from discord import app_commands -from discord.ext import commands +from disnake.ext import commands import json import os @@ -38,7 +38,7 @@ class Blacklist(commands.Cog): # ---------- Slash Commands ---------- @app_commands.command(name="blacklist", description="Blacklist un utilisateur") @app_commands.describe(user="Utilisateur à blacklister", reason="Raison") - async def blacklist_slash(self, interaction: discord.Interaction, user: discord.User, reason: str = "Aucune raison fournie"): + async def blacklist_slash(self, interaction: disnake.Interaction, 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) @@ -55,7 +55,7 @@ class Blacklist(commands.Cog): @app_commands.command(name="unblacklist", description="Retire un utilisateur de la blacklist") @app_commands.describe(user="Utilisateur à retirer") - async def unblacklist_slash(self, interaction: discord.Interaction, user: discord.User): + async def unblacklist_slash(self, interaction: disnake.Interaction, 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) @@ -81,13 +81,13 @@ class Blacklist(commands.Cog): 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 déblacklist", style=discord.ButtonStyle.danger) - async def open_ticket(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="📩 Ouvrir une demande de déblacklist", style=disnake.ButtonStyle.danger) + async def open_ticket(self, button: disnake.ui.Button, interaction: disnake.Interaction): if interaction.user.id != self.user_id: return await interaction.response.send_message("❌ Ce bouton ne t’appartient pas.", ephemeral=True) @@ -110,13 +110,13 @@ class TicketButton(discord.ui.View): await interaction.response.send_message("✅ Ton ticket a été créé en MP avec le bot.", ephemeral=True) -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) - async def close_ticket(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="❌ Fermer le ticket", style=disnake.ButtonStyle.danger) + async def close_ticket(self, button: disnake.ui.Button, interaction: disnake.Interaction): agents = load_json(AGENTS_FILE, {"agents": []}) if str(interaction.user.id) not in agents["agents"]: return await interaction.response.send_message("⛔ Seuls les agents Omega Kube peuvent fermer un ticket.", ephemeral=True) @@ -124,13 +124,13 @@ class CloseTicketView(discord.ui.View): 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) - async def confirm(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="✅ Oui", style=disnake.ButtonStyle.success) + async def confirm(self, button: disnake.ui.Button, interaction: disnake.Interaction): agents = load_json(AGENTS_FILE, {"agents": []}) if str(interaction.user.id) not in agents["agents"]: return await interaction.response.send_message("⛔ Tu n’as pas la permission.", ephemeral=True) @@ -142,12 +142,12 @@ class ConfirmCloseView(discord.ui.View): await interaction.channel.send("✅ Ticket fermé.") await interaction.channel.delete() - @discord.ui.button(label="❌ Non", style=discord.ButtonStyle.secondary) - async def cancel(self, interaction: discord.Interaction, button: discord.ui.Button): + @disnake.ui.button(label="❌ Non", style=disnake.ButtonStyle.secondary) + async def cancel(self, button: disnake.ui.Button, interaction: disnake.Interaction): await interaction.response.send_message("❌ Fermeture annulée.", ephemeral=True) # --------- Setup Cog ---------- -async def setup(bot: commands.Bot): +def setup(bot: commands.Bot): cog = Blacklist(bot) await bot.add_cog(cog) - await bot.tree.sync(guild=discord.Object(id=MAIN_GUILD_ID)) + await bot.tree.sync(guild=disnake.Object(id=MAIN_GUILD_ID)) diff --git a/commandes/absence_staff.py b/commandes/absence_staff.py index 53a79c3..689cc1e 100755 --- a/commandes/absence_staff.py +++ b/commandes/absence_staff.py @@ -83,12 +83,12 @@ def parse_date(date_str: str): class AbsenceModal(disnake.ui.Modal): def __init__(self, superieur: Optional[disnake.Member] = None): - super().__init__(title="Déclarer une Absence Staff") + super().__init__(title="Déclarer une Absence Staff", components=[self.motif, self.date_debut, self.date_fin]) self.superieur = superieur motif = disnake.ui.TextInput( label="Motif de l'absence", - style=disnake.TextStyle.paragraph, + style=disnake.TextInputStyle.paragraph, placeholder="Ex: Vacances, Raisons personnelles, Travail...", required=True, max_length=500 @@ -187,7 +187,7 @@ class AbsenceConfigView(disnake.ui.View): self.guild_id = guild_id @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): + async def set_channel(self, button: disnake.ui.Button, interaction: disnake.Interaction): await interaction.response.send_message("Mentionne le salon ici (ou envoie son ID) :", ephemeral=True) def check(m): @@ -214,7 +214,7 @@ class AbsenceConfigView(disnake.ui.View): await interaction.followup.send("❌ Temps écoulé.", ephemeral=True) @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): + async def set_role(self, button: disnake.ui.Button, interaction: disnake.Interaction): await interaction.response.send_message("Mentionne le rôle ici (ou envoie son ID) :", ephemeral=True) def check(m): @@ -499,7 +499,7 @@ class Absence(commands.Cog): settings = load_settings(message.guild.id) channel_id = settings.get("absence_channel_id") - absence_url = f"https://discord.com/channels/{message.guild.id}/{channel_id}/{msg_id}" if channel_id and msg_id else "" + absence_url = f"https://disnake.com/channels/{message.guild.id}/{channel_id}/{msg_id}" if channel_id and msg_id else "" reply_text = f"⚠️ {message.author.mention}, cet utilisateur est actuellement absent." if absence_url: @@ -507,5 +507,5 @@ class Absence(commands.Cog): await message.reply(reply_text, delete_after=30) -async def setup(bot): - await bot.add_cog(Absence(bot)) +def setup(bot): + bot.add_cog(Absence(bot)) diff --git a/commandes/backup.py b/commandes/backup.py index 5cafb7c..53c0293 100755 --- a/commandes/backup.py +++ b/commandes/backup.py @@ -268,5 +268,5 @@ class Backup(commands.Cog): await interaction.followup.send(embed=embed,ephemeral=True) -async def setup(bot: commands.Bot): - await bot.add_cog(Backup(bot)) +def setup(bot): + bot.add_cog(Backup(bot)) diff --git a/commandes/backup_restore.py b/commandes/backup_restore.py index d7f69cb..f51cdbc 100755 --- a/commandes/backup_restore.py +++ b/commandes/backup_restore.py @@ -198,5 +198,5 @@ class BackupRestore(commands.Cog): 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)) +def setup(bot): + bot.add_cog(BackupRestore(bot)) diff --git a/commandes/blacklist.py b/commandes/blacklist.py index a64f0b0..28d2288 100755 --- a/commandes/blacklist.py +++ b/commandes/blacklist.py @@ -200,7 +200,7 @@ class TicketButton(disnake.ui.View): self.user_id = user_id @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): + async def open_ticket(self, button: disnake.ui.Button, interaction: disnake.Interaction): if interaction.user.id != self.user_id: return await interaction.response.send_message("❌ Ce bouton ne t’appartient pas.", ephemeral=True) @@ -239,7 +239,7 @@ class CloseTicketView(disnake.ui.View): self.user_id = 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): + async def close_ticket(self, button: disnake.ui.Button, interaction: disnake.Interaction): 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) @@ -252,7 +252,7 @@ class ConfirmCloseView(disnake.ui.View): self.user_id = 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): + async def confirm(self, button: disnake.ui.Button, interaction: disnake.Interaction): 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) @@ -290,9 +290,9 @@ class ConfirmCloseView(disnake.ui.View): await interaction.response.send_message("✅ Ticket fermé avec succès.", ephemeral=True) @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): + async def cancel(self, button: disnake.ui.Button, interaction: disnake.Interaction): await interaction.response.send_message("❌ Fermeture annulée.", ephemeral=True) # --------- Setup --------- -async def setup(bot: commands.Bot): - await bot.add_cog(Blacklist(bot)) +def setup(bot): + bot.add_cog(Blacklist(bot)) diff --git a/commandes/bug_report.py b/commandes/bug_report.py index 6bbc736..0631984 100644 --- a/commandes/bug_report.py +++ b/commandes/bug_report.py @@ -34,7 +34,7 @@ def save_report(issue_iid, user_id): class BugReportModal(disnake.ui.Modal): def __init__(self, priority_choice): - super().__init__(title="Signaler un Bug") + super().__init__(title="Signaler un Bug", components=[self.bug_title, self.description]) self.priority_choice = priority_choice bug_title = disnake.ui.TextInput( @@ -46,7 +46,7 @@ class BugReportModal(disnake.ui.Modal): description = disnake.ui.TextInput( label="Description détaillée", - style=disnake.TextStyle.paragraph, + style=disnake.TextInputStyle.paragraph, placeholder="Que s'est-il passé ? Comment reproduire le bug ?", required=True, max_length=1000 @@ -77,7 +77,7 @@ class BugReportModal(disnake.ui.Modal): class FeatureSuggestionModal(disnake.ui.Modal): def __init__(self): - super().__init__(title="Suggérer une fonctionnalité") + super().__init__(title="Suggérer une fonctionnalité", components=[self.suggestion_title, self.description]) suggestion_title = disnake.ui.TextInput( label="Titre de la suggestion", @@ -88,7 +88,7 @@ class FeatureSuggestionModal(disnake.ui.Modal): description = disnake.ui.TextInput( label="Détails de la fonctionnalité", - style=disnake.TextStyle.paragraph, + style=disnake.TextInputStyle.paragraph, placeholder="Décrivez comment cela devrait fonctionner...", required=True, max_length=1000 @@ -334,5 +334,5 @@ class BugReport(commands.Cog): async def suggerer_fonctionnalite(self, interaction: disnake.ApplicationCommandInteraction): await interaction.response.send_modal(FeatureSuggestionModal()) -async def setup(bot): - await bot.add_cog(BugReport(bot)) +def setup(bot): + bot.add_cog(BugReport(bot)) diff --git a/commandes/convocation.py b/commandes/convocation.py index bb3921f..88253d7 100755 --- a/commandes/convocation.py +++ b/commandes/convocation.py @@ -274,5 +274,5 @@ class Convocation(commands.Cog): await interaction.response.send_message(embed=embed, ephemeral=True) -async def setup(bot): - await bot.add_cog(Convocation(bot)) +def setup(bot): + bot.add_cog(Convocation(bot)) diff --git a/commandes/goodbye.py b/commandes/goodbye.py index 9e4497b..ad3f87a 100644 --- a/commandes/goodbye.py +++ b/commandes/goodbye.py @@ -218,5 +218,5 @@ class Goodbye(commands.Cog): await interaction.response.send_message(msg, ephemeral=True) -async def setup(bot): - await bot.add_cog(Goodbye(bot)) \ No newline at end of file +def setup(bot): + bot.add_cog(Goodbye(bot)) \ No newline at end of file diff --git a/commandes/moderation.py b/commandes/moderation.py index 3f1174e..e250046 100644 --- a/commandes/moderation.py +++ b/commandes/moderation.py @@ -7,7 +7,7 @@ import re class ModReasonModal(disnake.ui.Modal): def __init__(self, parent_view, action_type): - super().__init__(title=f"Raison pour {action_type}") + super().__init__(title=f"Raison pour {action_type}", components=[]) self.parent_view = parent_view self.action_type = action_type @@ -17,7 +17,7 @@ class ModReasonModal(disnake.ui.Modal): required=True, max_length=500 ) - self.add_item(self.reason) + self.append_component(self.reason) if action_type == "TIMEOUT": self.duration = disnake.ui.TextInput( @@ -26,7 +26,7 @@ class ModReasonModal(disnake.ui.Modal): required=True, max_length=10 ) - self.add_item(self.duration) + self.append_component(self.duration) async def callback(self, interaction: disnake.Interaction): reason = self.reason.value @@ -78,43 +78,43 @@ class ModPanelView(disnake.ui.View): await interaction.edit_original_response(embed=embed, view=self) @disnake.ui.button(label="Avertir", style=disnake.ButtonStyle.secondary, emoji="⚠️") - async def warn_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def warn_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): await interaction.response.send_modal(ModReasonModal(self, "WARN")) @disnake.ui.button(label="Timeout", style=disnake.ButtonStyle.secondary, emoji="⏳") - async def timeout_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def timeout_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): await interaction.response.send_modal(ModReasonModal(self, "TIMEOUT")) @disnake.ui.button(label="Expulser", style=disnake.ButtonStyle.secondary, emoji="👢") - async def kick_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def kick_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): await interaction.response.send_modal(ModReasonModal(self, "KICK")) @disnake.ui.button(label="Bannir", style=disnake.ButtonStyle.danger, emoji="🔨") - async def ban_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def ban_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): await interaction.response.send_modal(ModReasonModal(self, "BAN")) @disnake.ui.button(label="Historique", style=disnake.ButtonStyle.primary, emoji="📜") - async def history_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def history_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): cog = interaction.bot.get_cog("Moderation") await cog.history(interaction, self.member) @disnake.ui.button(label="Clear Warns", style=disnake.ButtonStyle.danger, emoji="🧹") - async def clear_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def clear_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): cog = interaction.bot.get_cog("Moderation") await cog.clearwarns(interaction, self.member, reason="Nettoyage via Panel") await self.update_panel(interaction) class ModConfigModal(disnake.ui.Modal): def __init__(self, field_name, current_value): - super().__init__(title=f"Modifier {field_name}") + super().__init__(components=[], title=f"Modifier {field_name}") self.field_name = field_name self.input = disnake.ui.TextInput( label=field_name, - default=str(current_value), + value=str(current_value), placeholder="Entrez une valeur numérique...", required=True ) - self.add_item(self.input) + self.append_component(self.input) async def callback(self, interaction: disnake.Interaction): value = self.input.value @@ -146,7 +146,7 @@ class ModConfigView(disnake.ui.View): self.db_path = db_path @disnake.ui.button(label="Log Channel", style=disnake.ButtonStyle.primary, emoji="📁") - async def log_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def log_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): view = disnake.ui.View() select = disnake.ui.ChannelSelect( placeholder="Sélectionnez le salon de logs...", @@ -166,7 +166,7 @@ class ModConfigView(disnake.ui.View): await interaction.response.send_message("Choisissez le salon de logs :", view=view, ephemeral=True) @disnake.ui.button(label="Paliers Warns", style=disnake.ButtonStyle.secondary, emoji="⚖️") - async def limits_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def limits_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): 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,)) @@ -187,7 +187,7 @@ class ModConfigView(disnake.ui.View): await interaction.response.send_message("Quelle limite modifier ?", view=view, ephemeral=True) @disnake.ui.button(label="Durée Timeout", style=disnake.ButtonStyle.secondary, emoji="⏱️") - async def duration_btn(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def duration_btn(self, button: disnake.ui.Button, interaction: disnake.Interaction): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute("SELECT timeout_duration FROM mod_config WHERE guild_id = ?", (self.guild_id,)) @@ -320,5 +320,5 @@ class Moderation(commands.Cog): if not interaction.response.is_done(): await interaction.response.send_message(f"✅ Warns de {member.name} effacés.", ephemeral=True) else: await interaction.followup.send(f"✅ Warns de {member.name} effacés.", ephemeral=True) -async def setup(bot): - await bot.add_cog(Moderation(bot)) +def setup(bot): + bot.add_cog(Moderation(bot)) diff --git a/commandes/modules_security/antiraid.py b/commandes/modules_security/antiraid.py index 6ad47ad..0244034 100644 --- a/commandes/modules_security/antiraid.py +++ b/commandes/modules_security/antiraid.py @@ -56,5 +56,5 @@ class AntiRaid(commands.Cog): 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): - await bot.add_cog(AntiRaid(bot)) \ No newline at end of file +def setup(bot): + bot.add_cog(AntiRaid(bot)) \ No newline at end of file diff --git a/commandes/modules_security/antispam.py b/commandes/modules_security/antispam.py index fef7ba2..19c8969 100644 --- a/commandes/modules_security/antispam.py +++ b/commandes/modules_security/antispam.py @@ -13,7 +13,7 @@ WHITELIST_FILE = "data/whitelist.json" CACHE_TTL = 5 # Triggers pour détection de liens -LINK_TRIGGERS = ("discord.gg/", "http://", "https://", "www.") +LINK_TRIGGERS = ("disnake.gg/", "http://", "https://", "www.") class AntiSpam(commands.Cog): def __init__(self, bot): @@ -181,5 +181,5 @@ class AntiSpam(commands.Cog): except Exception as e: print(f"Erreur lors de la sanction : {e}") -async def setup(bot): - await bot.add_cog(AntiSpam(bot)) \ No newline at end of file +def setup(bot): + bot.add_cog(AntiSpam(bot)) \ No newline at end of file diff --git a/commandes/modules_security/logs_manager.py b/commandes/modules_security/logs_manager.py index dc3238c..ce97861 100644 --- a/commandes/modules_security/logs_manager.py +++ b/commandes/modules_security/logs_manager.py @@ -58,5 +58,5 @@ class LogsManager(commands.Cog): ) await log_chan.send(embed=embed) -async def setup(bot): - await bot.add_cog(LogsManager(bot)) \ No newline at end of file +def setup(bot): + bot.add_cog(LogsManager(bot)) \ No newline at end of file diff --git a/commandes/modules_security/rules.py b/commandes/modules_security/rules.py index 6f5999a..2373eb8 100644 --- a/commandes/modules_security/rules.py +++ b/commandes/modules_security/rules.py @@ -38,7 +38,7 @@ def save_settings(guild_id: int, settings: dict) -> None: # --- MODALS DE CONFIGURATION DU RÈGLEMENT (BACK-END) --- -class RulesTitleModal(disnake.ui.Modal, title="📜 Titre du Règlement"): +class RulesTitleModal(disnake.ui.Modal): """Modal pour configurer le titre du règlement""" title_input = disnake.ui.TextInput( label="Titre du règlement", @@ -49,12 +49,12 @@ class RulesTitleModal(disnake.ui.Modal, title="📜 Titre du Règlement"): ) def __init__(self, guild_id: int, settings: dict, on_update_callback=None): - super().__init__() + super().__init__(title="📜 Titre du Règlement", components=[self.title_input]) self.guild_id = guild_id self.settings = settings self.on_update_callback = on_update_callback - async def on_submit(self, interaction: disnake.Interaction): + async def callback(self, interaction: disnake.Interaction): self.settings["rules_title"] = self.title_input.value save_settings(self.guild_id, self.settings) @@ -77,24 +77,24 @@ class RulesTitleModal(disnake.ui.Modal, title="📜 Titre du Règlement"): return view -class RulesContentModal(disnake.ui.Modal, title="📝 Contenu du Règlement"): +class RulesContentModal(disnake.ui.Modal): """Modal pour configurer le contenu du règlement""" content_input = disnake.ui.TextInput( label="Contenu du règlement", placeholder="Entrez les règles de votre serveur...", - style=disnake.TextStyle.paragraph, + style=disnake.TextInputStyle.paragraph, min_length=10, max_length=4000, required=True ) def __init__(self, guild_id: int, settings: dict, on_update_callback=None): - super().__init__() + super().__init__(title="📝 Contenu du Règlement", components=[self.content_input]) self.guild_id = guild_id self.settings = settings self.on_update_callback = on_update_callback - async def on_submit(self, interaction: disnake.Interaction): + async def callback(self, interaction: disnake.Interaction): self.settings["rules_content"] = self.content_input.value save_settings(self.guild_id, self.settings) @@ -117,7 +117,7 @@ class RulesContentModal(disnake.ui.Modal, title="📝 Contenu du Règlement"): return view -class RulesChannelModal(disnake.ui.Modal, title="📌 Salon du Règlement"): +class RulesChannelModal(disnake.ui.Modal): """Modal pour sélectionner le salon du règlement""" channel_input = disnake.ui.TextInput( label="ID du salon", @@ -128,12 +128,12 @@ class RulesChannelModal(disnake.ui.Modal, title="📌 Salon du Règlement"): ) def __init__(self, guild_id: int, settings: dict, on_update_callback=None): - super().__init__() + super().__init__(title="📌 Salon du Règlement", components=[self.channel_input]) self.guild_id = guild_id self.settings = settings self.on_update_callback = on_update_callback - async def on_submit(self, interaction: disnake.Interaction): + async def callback(self, interaction: disnake.Interaction): if not self.channel_input.value.isdigit(): return await interaction.response.send_message( "❌ Erreur : ID invalide.", @@ -171,7 +171,7 @@ class RulesChannelModal(disnake.ui.Modal, title="📌 Salon du Règlement"): return view -class RulesRoleModal(disnake.ui.Modal, title="✅ Rôle d'Acceptation"): +class RulesRoleModal(disnake.ui.Modal): """Modal pour configurer le rôle attribué à l'acceptation""" role_input = disnake.ui.TextInput( label="ID du rôle", @@ -182,12 +182,12 @@ class RulesRoleModal(disnake.ui.Modal, title="✅ Rôle d'Acceptation"): ) def __init__(self, guild_id: int, settings: dict, on_update_callback=None): - super().__init__() + super().__init__(title="✅ Rôle d'Acceptation", components=[self.role_input]) self.guild_id = guild_id self.settings = settings self.on_update_callback = on_update_callback - async def on_submit(self, interaction: disnake.Interaction): + async def callback(self, interaction: disnake.Interaction): if not self.role_input.value.isdigit(): return await interaction.response.send_message( "❌ Erreur : ID invalide.", @@ -225,7 +225,7 @@ class RulesRoleModal(disnake.ui.Modal, title="✅ Rôle d'Acceptation"): return view -class RulesMessageTypeModal(disnake.ui.Modal, title="📝 Type de Message"): +class RulesMessageTypeModal(disnake.ui.Modal): """Modal pour choisir le type de message du règlement""" message_type = disnake.ui.TextInput( label="Type de message", @@ -236,12 +236,12 @@ class RulesMessageTypeModal(disnake.ui.Modal, title="📝 Type de Message"): ) def __init__(self, guild_id: int, settings: dict, on_update_callback=None): - super().__init__() + super().__init__(title="📝 Type de Message", components=[self.message_type]) self.guild_id = guild_id self.settings = settings self.on_update_callback = on_update_callback - async def on_submit(self, interaction: disnake.Interaction): + async def callback(self, interaction: disnake.Interaction): msg_type = self.message_type.value.lower().strip() if msg_type not in ["embed", "simple"]: @@ -323,13 +323,13 @@ class RulesAcceptButton(disnake.ui.View): self.guild_id = guild_id self.accept_role_id = accept_role_id - @discord.ui.button( + @disnake.ui.button( label="J'accepte le règlement", emoji="✅", style=disnake.ButtonStyle.green, custom_id="rules_accept_button" ) - async def accept_rules(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def accept_rules(self, button: disnake.ui.Button, interaction: disnake.Interaction): """Gère le clic sur le bouton d'acceptation""" guild = interaction.guild @@ -495,8 +495,8 @@ class RulesCog(commands.Cog): return title, content -async def setup(bot): +def setup(bot): """Setup du cog de règlement""" cog = RulesCog(bot) - await bot.add_cog(cog) + bot.add_cog(cog) diff --git a/commandes/no_link.py b/commandes/no_link.py index e3ad27b..3ce67b6 100755 --- a/commandes/no_link.py +++ b/commandes/no_link.py @@ -47,5 +47,5 @@ class NoLink(commands.Cog): print(f"Erreur lors de la suppression du message : {exc}") -async def setup(bot: commands.Bot) -> None: - await bot.add_cog(NoLink(bot)) +def setup(bot: commands.Bot) -> None: + bot.add_cog(NoLink(bot)) diff --git a/commandes/scan.py b/commandes/scan.py index db9c1d4..1ccb2c9 100755 --- a/commandes/scan.py +++ b/commandes/scan.py @@ -77,5 +77,5 @@ class Scan(commands.Cog): await interaction.followup.send(f"✅ Fichier sain.\nHash : `{result['hash']}`") -async def setup(bot: commands.Bot): - await bot.add_cog(Scan(bot)) +def setup(bot): + bot.add_cog(Scan(bot)) diff --git a/commandes/security.py b/commandes/security.py index a839a93..4e3909c 100755 --- a/commandes/security.py +++ b/commandes/security.py @@ -69,7 +69,7 @@ def is_immune(guild, user) -> bool: # --- MODALS DE CONFIGURATION --- -class SpamThresholdModal(disnake.ui.Modal, title="🛡️ Seuil Anti-Spam"): +class SpamThresholdModal(disnake.ui.Modal): """Modal pour configurer le seuil anti-spam""" threshold_input = disnake.ui.TextInput( label="Messages max autorisés", @@ -80,12 +80,12 @@ class SpamThresholdModal(disnake.ui.Modal, title="🛡️ Seuil Anti-Spam"): ) def __init__(self, guild_id: int, settings: dict, view: 'SecurityView'): - super().__init__() + super().__init__(title="🛡️ Seuil Anti-Spam", components=[self.threshold_input]) self.guild_id = guild_id self.settings = settings self.parent_view = view - async def on_submit(self, interaction: disnake.Interaction): + async def callback(self, interaction: disnake.Interaction): if not self.threshold_input.value.isdigit(): return await interaction.response.send_message( "❌ Erreur : Veuillez entrer un nombre entier.", @@ -110,7 +110,7 @@ class SpamThresholdModal(disnake.ui.Modal, title="🛡️ Seuil Anti-Spam"): ephemeral=True ) -class RaidThresholdModal(disnake.ui.Modal, title="⚔️ Seuil Anti-Raid"): +class RaidThresholdModal(disnake.ui.Modal): """Modal pour configurer le seuil anti-raid""" threshold_input = disnake.ui.TextInput( label="Joins max en 10 secondes", @@ -121,12 +121,12 @@ class RaidThresholdModal(disnake.ui.Modal, title="⚔️ Seuil Anti-Raid"): ) def __init__(self, guild_id: int, settings: dict, view: 'SecurityView'): - super().__init__() + super().__init__(title="⚔️ Seuil Anti-Raid", components=[self.threshold_input]) self.guild_id = guild_id self.settings = settings self.parent_view = view - async def on_submit(self, interaction: disnake.Interaction): + async def callback(self, interaction: disnake.Interaction): if not self.threshold_input.value.isdigit(): return await interaction.response.send_message( "❌ Erreur : Veuillez entrer un nombre entier.", @@ -153,7 +153,7 @@ class RaidThresholdModal(disnake.ui.Modal, title="⚔️ Seuil Anti-Raid"): # --- MODALS DE CONFIGURATION WHITELIST --- -class WhitelistModal(disnake.ui.Modal, title="👤 Ajouter à la Whitelist"): +class WhitelistModal(disnake.ui.Modal): """Modal pour ajouter un utilisateur à la whitelist""" user_input = disnake.ui.TextInput( label="ID de l'utilisateur", @@ -164,11 +164,11 @@ class WhitelistModal(disnake.ui.Modal, title="👤 Ajouter à la Whitelist"): ) def __init__(self, guild_id: int, view: 'SecurityView'): - super().__init__() + super().__init__(title="👤 Ajouter à la Whitelist", components=[self.user_input]) self.guild_id = guild_id self.parent_view = view - async def on_submit(self, interaction: disnake.Interaction): + async def callback(self, interaction: disnake.Interaction): if not self.user_input.value.isdigit(): return await interaction.response.send_message( "❌ Erreur : ID invalide.", @@ -873,7 +873,7 @@ class MainCategorySelect(disnake.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'): + def __init__(self, guild: disnake.Guild, view: 'SecurityView'): self.guild = guild self.parent_view = view whitelist = load_whitelist(guild.id) @@ -950,11 +950,11 @@ class Security(commands.Cog): def __init__(self, bot): self.bot = bot - @app_commands.command( + @commands.slash_command( name="security", description="🛡️ Ouvrir le panneau de sécurité Kuby" ) - async def security(self, interaction: disnake.Interaction): + async def security(self, interaction: disnake.ApplicationCommandInteraction): """ Commande principale pour ouvrir l'interface de sécurité. Accessible uniquement aux administrateurs et propriétaires whitelistés. @@ -989,12 +989,12 @@ class Security(commands.Cog): ) @security.error - async def security_error(self, interaction: disnake.Interaction, error: app_commands.AppCommandError): + async def security_error(self, interaction: disnake.Interaction, error: Exception): """Gestion des erreurs de la commande security""" # Utilisation de followup si l'interaction est déjà différée ou répondue send_method = interaction.followup.send if interaction.response.is_done() else interaction.response.send_message - if isinstance(error, app_commands.MissingPermissions): + if isinstance(error, commands.MissingPermissions): await send_method( "❌ Permissions insuffisantes.", ephemeral=True @@ -1006,7 +1006,7 @@ class Security(commands.Cog): ) -async def setup(bot): +def setup(bot): """Setup du cog de sécurité""" - await bot.add_cog(Security(bot)) + bot.add_cog(Security(bot)) diff --git a/commandes/sendbotmessages.py b/commandes/sendbotmessages.py index 058c2e4..e77819e 100755 --- a/commandes/sendbotmessages.py +++ b/commandes/sendbotmessages.py @@ -20,6 +20,6 @@ class SentBotMessages(commands.Cog): except Exception as e: await interaction.response.send_message(f"❌ Une erreur est survenue : {e}", ephemeral=True) -async def setup(bot): - await bot.add_cog(SentBotMessages(bot)) +def setup(bot): + bot.add_cog(SentBotMessages(bot)) diff --git a/commandes/snipe.py b/commandes/snipe.py index f47cacc..60b4b20 100755 --- a/commandes/snipe.py +++ b/commandes/snipe.py @@ -109,5 +109,5 @@ class SnipeCommands(commands.Cog): ephemeral=True ) -async def setup(bot): - await bot.add_cog(SnipeCommands(bot)) +def setup(bot): + bot.add_cog(SnipeCommands(bot)) diff --git a/commandes/staff_leaderboard.py b/commandes/staff_leaderboard.py index 9d1007d..d3b7b86 100644 --- a/commandes/staff_leaderboard.py +++ b/commandes/staff_leaderboard.py @@ -68,5 +68,5 @@ class StaffLeaderboard(commands.Cog): await interaction.followup.send("Une erreur est survenue lors de la génération du classement.", ephemeral=True) -async def setup(bot): - await bot.add_cog(StaffLeaderboard(bot)) \ No newline at end of file +def setup(bot): + bot.add_cog(StaffLeaderboard(bot)) \ No newline at end of file diff --git a/commandes/staff_ratings.py b/commandes/staff_ratings.py index a777da0..e44b0dd 100644 --- a/commandes/staff_ratings.py +++ b/commandes/staff_ratings.py @@ -133,5 +133,5 @@ class StaffRatings(commands.Cog): return buf -async def setup(bot): - await bot.add_cog(StaffRatings(bot)) \ No newline at end of file +def setup(bot): + bot.add_cog(StaffRatings(bot)) \ No newline at end of file diff --git a/commandes/ticket.py b/commandes/ticket.py.old similarity index 100% rename from commandes/ticket.py rename to commandes/ticket.py.old diff --git a/commandes/ticket/__init__.py b/commandes/ticket/__init__.py index 1b2441b..99bfe6d 100644 --- a/commandes/ticket/__init__.py +++ b/commandes/ticket/__init__.py @@ -38,6 +38,8 @@ from .utils.formatters import TicketEmbedFormatter from .staff_analytics import StaffAnalytics, StaffStatsView from .utils.permissions import is_staff, is_whitelisted, can_access_config from src.logger import kuby_logger +# V2 RICH COMPONENTS LOADED - 14:10 +kuby_logger.info("🎫 [TicketSystem] Chargement de la version V2 Rich Components...") # Add a task for sending feedback reminders class FeedbackReminderTask: @@ -103,6 +105,43 @@ class TicketPanelView(disnake.ui.View): ) staff_ratings_btn.callback = self._staff_ratings_callback self.add_item(staff_ratings_btn) + + def get_components_v2(self): + """Retourne les composants V2 pour un affichage moderne""" + return [ + disnake.ui.Container( + disnake.ui.Section( + "# 🎫 Système de Support - Omega Kube", + accessory=disnake.ui.Button( + label="Créer un ticket", + style=disnake.ButtonStyle.blurple, + custom_id="ticket_create" + ) + ), + disnake.ui.Separator(divider=True), + disnake.ui.TextDisplay( + content="Bienvenue sur le système de support d'**Omega Kube**.\n" + "Notre équipe est à votre disposition pour vous aider dans les plus brefs délais." + ), + disnake.ui.Separator(divider=True), + disnake.ui.Section( + f"📂 {len(self.config.categories)} Catégories disponibles", + accessory=disnake.ui.Button( + label="Mes tickets", + style=disnake.ButtonStyle.secondary, + custom_id="ticket_my_tickets" + ) + ), + disnake.ui.Section( + "📊 Administration & Staff", + accessory=disnake.ui.Button( + label="Analytics", + style=disnake.ButtonStyle.primary, + custom_id="ticket_analytics" + ) + ) + ) + ] # Configuration (admin only) config_btn = disnake.ui.Button( @@ -132,23 +171,42 @@ class TicketPanelView(disnake.ui.View): await interaction.response.send_message("Aucune catégorie configurée. Utilisez la configuration.", ephemeral=True) return - # Créer view avec les catégories - view = CategorySelectionView(self.bot, self.config) - - embed = disnake.Embed( - title="Créer un Ticket", - description="Sélectionnez une catégorie ci-dessous:", - color=disnake.Color.blurple() - ) + # Build V2 Container for category selection + sections = [ + disnake.ui.TextDisplay(content="## 🎫 Créer un Ticket"), + disnake.ui.Separator(), + ] for cat in self.config.categories[:5]: - embed.add_field( - name=f"{cat.emoji} {cat.name}", - value=cat.description or "Aucune description", - inline=True + desc = cat.description or "Aucune description" + sections.append( + disnake.ui.Section( + f"**{cat.emoji} {cat.name}**\n{desc}", + accessory=disnake.ui.Button( + label=cat.name, + style=disnake.ButtonStyle.primary, + custom_id=f"cat_{cat.id}_{int(disnake.utils.time_snowflake(disnake.utils.utcnow()) / 1000)}" + ) + ) ) - await interaction.response.send_message(embed=embed, view=view, ephemeral=True) + try: + await interaction.response.send_message( + components=[disnake.ui.Container(*sections)], + flags=disnake.MessageFlags(is_components_v2=True), + ephemeral=True + ) + except Exception as e: + import traceback + traceback.print_exc() + # Fallback: texte simple avec view legacy + cat_list = "\n".join(f"{cat.emoji} **{cat.name}** — {cat.description or 'Aucune description'}" for cat in self.config.categories[:5]) + if not interaction.response.is_done(): + await interaction.response.send_message( + f"**🎫 Créer un Ticket**\n\nSélectionnez une catégorie ci-dessous:", + view=CategorySelectionView(self.bot, self.config), + ephemeral=True + ) async def _my_tickets_callback(self, interaction: disnake.Interaction): """Affiche les tickets de l'utilisateur""" @@ -166,22 +224,24 @@ class TicketPanelView(disnake.ui.View): await interaction.response.send_message("Vous n'avez aucun ticket ouvert.", ephemeral=True) return - embed = disnake.Embed( - title="Vos Tickets Ouverts", - description=f"Vous avez **{len(tickets)}** ticket(s) ouvert(s):", - color=disnake.Color.blue() - ) + # Build V2 Container for user's open tickets + sections = [ + disnake.ui.TextDisplay(content=f"## 🎫 Vos Tickets Ouverts"), + disnake.ui.TextDisplay(content=f"Vous avez **{len(tickets)}** ticket(s) ouvert(s):"), + disnake.ui.Separator(), + ] for ticket in tickets[:5]: channel = interaction.guild.get_channel(ticket.channel_id) channel_mention = channel.mention if channel else "Salon supprimé" - embed.add_field( - name=f"Ticket #{ticket.channel_id}", - value=f"Salon: {channel_mention}\nStatut: {ticket.status.value}\nCréé: {ticket.created_at.strftime('%d/%m/%Y')}", - inline=True - ) + sections.append(disnake.ui.TextDisplay( + content=f"**Ticket #{ticket.channel_id}**\nSalon: {channel_mention}\nStatut: {ticket.status.value}\nCréé: {ticket.created_at.strftime('%d/%m/%Y')}" + )) - await interaction.response.send_message(embed=embed, ephemeral=True) + await interaction.response.send_message( + components=[disnake.ui.Container(*sections)], + ephemeral=True + ) async def _analytics_callback(self, interaction: disnake.Interaction): """Affiche les analytics (staff uniquement)""" @@ -467,7 +527,16 @@ class CategorySelectionView(disnake.ui.View): # Check limits user_tickets = storage.get_user_tickets(interaction.guild_id, interaction.user.id, open_only=True) - if len(user_tickets) >= self.config.max_tickets_per_user: + active_tickets = [] + for t in user_tickets: + if interaction.guild.get_channel(t.channel_id): + active_tickets.append(t) + else: + # Automatically clean up phantom tickets + t.status = TicketStatus.CLOSED + storage.save_ticket(t) + + if len(active_tickets) >= self.config.max_tickets_per_user: await interaction.response.send_message( f"Vous avez atteint la limite de {self.config.max_tickets_per_user} tickets.", ephemeral=True @@ -487,35 +556,24 @@ class CategorySelectionView(disnake.ui.View): class TicketPriorityModal(disnake.ui.Modal): - """Modal pour sélectionner la priorité et la raison""" - - def __init__(self, bot, config: GuildTicketConfig, category: TicketCategory): - super().__init__(title=f" {category.name}") + """Modal pour spécifier la raison du ticket""" + def __init__(self, bot, config, category: TicketCategory): + self.reason_input = disnake.ui.TextInput( + label="Motif du ticket", + custom_id="ticket_reason", + placeholder="Décrivez brièvement votre problème...", + required=category.require_reason, + min_length=5 if category.require_reason else 0, + max_length=250, + style=disnake.TextInputStyle.paragraph + ) + super().__init__(title=f"Support: {category.name}", components=[self.reason_input]) self.bot = bot self.config = config self.category = category - - # Priorité - self.priority = disnake.ui.TextInput( - label="Priorité", - placeholder="Normale, Haute ou Urgente", - default="Normale", - required=True, - max_length=10 - ) - self.add_item(self.priority) - - # Raison - self.reason = disnake.ui.TextInput( - label="Motif de votre demande", - placeholder="Décrivez votre demande...", - style=disnake.TextStyle.paragraph, - required=True, - max_length=500 - ) - self.add_item(self.reason) - - async def on_submit(self, interaction: disnake.Interaction): + + async def callback(self, interaction: disnake.ModalInteraction): + await interaction.response.defer(ephemeral=True) try: # Map priority priority_map = { @@ -523,12 +581,14 @@ class TicketPriorityModal(disnake.ui.Modal): "Haute": Priority.HIGH, "Normale": Priority.NORMAL } - priority = priority_map.get(self.priority.value, Priority.NORMAL) + # Logic updated to use reason input + priority = Priority.NORMAL + reason = interaction.text_values.get("ticket_reason", "Aucun motif fourni") # Create ticket - await self._create_ticket(interaction, priority, self.reason.value) + await self._create_ticket(interaction, priority, reason) except Exception as e: - await interaction.response.send_message(f"Erreur lors de la création du ticket: {e}", ephemeral=True) + await interaction.followup.send(f"Erreur lors de la création du ticket: {e}", ephemeral=True) async def on_error(self, interaction: disnake.Interaction, error: Exception): """Handle errors in the modal""" @@ -572,6 +632,8 @@ class TicketPriorityModal(disnake.ui.Modal): if self.category.discord_category_id: try: category_channel = guild.get_channel(int(self.category.discord_category_id)) + if category_channel and not isinstance(category_channel, disnake.CategoryChannel): + category_channel = None except (ValueError, TypeError): pass @@ -579,6 +641,8 @@ class TicketPriorityModal(disnake.ui.Modal): if not category_channel and self.config.default_category: try: category_channel = guild.get_channel(int(self.config.default_category)) + if category_channel and not isinstance(category_channel, disnake.CategoryChannel): + category_channel = None except (ValueError, TypeError): pass @@ -589,7 +653,7 @@ class TicketPriorityModal(disnake.ui.Modal): topic=f"Ticket de {user} | Catégorie: {self.category.name}" ) except Exception as e: - await interaction.response.send_message(f"Erreur: {e}", ephemeral=True) + await interaction.followup.send(f"Erreur: {e}", ephemeral=True) return # Create ticket data @@ -604,19 +668,7 @@ class TicketPriorityModal(disnake.ui.Modal): ) storage.save_ticket(ticket_data) - # Send welcome message - 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=disnake.Color.blurple() - ) - embed.add_field(name="Motif", value=reason, inline=False) - embed.add_field(name="Priorité", value=priority.value.capitalize(), inline=True) - embed.set_footer(text=f"Ticket #{ticket_channel.id}") - - # Add management buttons - view = TicketManagementView(self.bot, storage, self.config, ticket_data) - # Prepare mentions + # Build mentions mentions = [user.mention] for role_id in self.config.staff_roles: mentions.append(f"<@&{role_id}>") @@ -625,9 +677,13 @@ class TicketPriorityModal(disnake.ui.Modal): mention_content = " ".join(mentions) - # Add management buttons - view = TicketManagementView(self.bot, storage, self.config, ticket_data) - await ticket_channel.send(content=mention_content, embed=embed, view=view) + # Build a single V2 container with mention and ticket management components + mention_section = disnake.ui.TextDisplay(content=mention_content) + # Retrieve existing management container + base_container = get_ticket_management_container(ticket_data, self.category, self.config)[0] + # Build new container merging mention and existing sections + combined_container = disnake.ui.Container(mention_section, *base_container.children) + await ticket_channel.send(components=[combined_container]) # Log if self.config.log_channel_id: @@ -643,28 +699,25 @@ class TicketPriorityModal(disnake.ui.Modal): log_embed.add_field(name="Salon", value=ticket_channel.mention, inline=True) await log_channel.send(embed=log_embed) - await interaction.response.send_message(f"Ticket créé: {ticket_channel.mention}", ephemeral=True) + await interaction.followup.send(f"Ticket créé: {ticket_channel.mention}", ephemeral=True) 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): - super().__init__(title="Définir la Catégorie Discord") + self.category_input = disnake.ui.TextInput( + label="Catégorie Discord", + placeholder="Entrez l'ID de la catégorie...", + required=True + ) + super().__init__(title="Définir la Catégorie", components=[self.category_input]) self.bot = bot self.storage = storage self.config = config - - self.category = disnake.ui.TextInput( - label="Catégorie Discord", - placeholder="Entrez l'ID de la catégorie (clic droit -> Copier l'ID)", - required=True, - max_length=20 - ) - self.add_item(self.category) - async def on_submit(self, interaction: disnake.Interaction): - cat_id = self.category.value.strip() + async def callback(self, interaction: disnake.ModalInteraction): + cat_id = self.category_input.value.strip() # Verify it's a valid ID if not cat_id.isdigit(): @@ -683,8 +736,51 @@ class SetCategoryModal(disnake.ui.Modal): await interaction.response.send_message(f"📁 Les tickets seront désormais créés dans la catégorie **{category.name}**!", ephemeral=True) +def get_ticket_management_container(ticket: TicketData, category, config: GuildTicketConfig) -> list: + """Helper V2 pour générer les actions de gestion d'un ticket""" + sections = [] + + # Informations du ticket (Titre, description) + sections.append(disnake.ui.TextDisplay(content=f"# {category.emoji} {category.name}")) + sections.append(disnake.ui.Separator()) + + status_emoji = "🟢" if ticket.status == TicketStatus.OPEN else ("🟡" if ticket.status == TicketStatus.CLAIMED else "🔴") + status_text = "Ouvert" if ticket.status == TicketStatus.OPEN else ("Réclamé" if ticket.status == TicketStatus.CLAIMED else "Fermé") + + content = f"Bienvenue <@{ticket.user_id}>!\n\nVotre demande sera traitée sous peu par notre équipe.\n\n" + content += f"**Motif:** {ticket.reason or 'Aucun'}\n" + content += f"**Priorité:** {ticket.priority.value.capitalize()}\n" + content += f"**Statut:** {status_emoji} {status_text}" + + if ticket.claimed_by: + content += f"\n**Pris en charge par:** <@{ticket.claimed_by}>" + + sections.append(disnake.ui.TextDisplay(content=content)) + sections.append(disnake.ui.Separator()) + + # 1. Gestion Staff + if ticket.status == TicketStatus.OPEN and config.claim_enabled and category and category.allow_claims: + sections.append(disnake.ui.Section("🤝 Le staff peut prendre en charge le ticket", accessory=disnake.ui.Button(label="Réclamer", style=disnake.ButtonStyle.primary, custom_id="ticket_claim"))) + elif ticket.status == TicketStatus.CLAIMED: + sections.append(disnake.ui.Section("🤝 Gérer la réclamation", accessory=disnake.ui.Button(label="Abandonner", style=disnake.ButtonStyle.danger, custom_id="ticket_unclaim"))) + sections.append(disnake.ui.Section("🤝 Transférer", accessory=disnake.ui.Button(label="Transférer", style=disnake.ButtonStyle.secondary, custom_id="ticket_claim"))) + + # 2. Séparateur + if len(sections) > 0: + sections.append(disnake.ui.Separator(divider=True)) + + # 3. Actions générales + if ticket.status in [TicketStatus.OPEN, TicketStatus.CLAIMED]: + sections.append(disnake.ui.Section("🔒 Fermer le ticket", accessory=disnake.ui.Button(label="Fermer", style=disnake.ButtonStyle.danger, custom_id="ticket_close"))) + elif ticket.status == TicketStatus.CLOSED: + sections.append(disnake.ui.Section("🔓 Rouvrir le ticket", accessory=disnake.ui.Button(label="Rouvrir", style=disnake.ButtonStyle.success, custom_id="ticket_reopen"))) + + sections.append(disnake.ui.Section("📜 Générer un transcript", accessory=disnake.ui.Button(label="Transcript", style=disnake.ButtonStyle.secondary, custom_id="ticket_transcript"))) + + return [disnake.ui.Container(*sections)] + class TicketManagementView(disnake.ui.View): - """Gestion du ticket (dans le salon du ticket)""" + """Gestion du ticket (dans le salon du ticket) - Uniquement pour les callbacks désormais""" def __init__(self, bot, storage, config: GuildTicketConfig, ticket: TicketData): super().__init__(timeout=None) @@ -693,47 +789,16 @@ class TicketManagementView(disnake.ui.View): self.config = config self.ticket = ticket self.category = config.get_category(ticket.category_id) - - # 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 = disnake.ui.Button( - style=disnake.ButtonStyle.primary, - label="Réclamer" if ticket.status == TicketStatus.OPEN else "Transférer", - custom_id="ticket_claim" - ) - claim_btn.callback = self._claim_callback - self.add_item(claim_btn) - - # Fermer - close_btn = disnake.ui.Button( - style=disnake.ButtonStyle.red, - label="Fermer", - custom_id="ticket_close" - ) - close_btn.callback = self._close_callback - self.add_item(close_btn) - - # Transcripts - transcript_btn = disnake.ui.Button( - style=disnake.ButtonStyle.secondary, - label="Transcript", - custom_id="ticket_transcript" - ) - transcript_btn.callback = self._transcript_callback - self.add_item(transcript_btn) - - # Reopen (if closed) - if ticket.status == TicketStatus.CLOSED: - 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: disnake.Interaction): """Demande confirmation avant de fermer le ticket""" + kuby_logger.info( + f"[TicketSystem] _close_callback: " + f"guild={getattr(interaction.guild,'id',None)} " + f"channel={getattr(interaction.channel,'id',None)} " + f"user={interaction.user.id} " + f"ticket_user={getattr(self.ticket,'user_id',None)}" + ) # Vérifier si l'utilisateur peut fermer le ticket can_close = ( interaction.user.id == self.ticket.user_id or @@ -744,27 +809,39 @@ class TicketManagementView(disnake.ui.View): await interaction.response.send_message("Vous ne pouvez pas fermer ce ticket.", ephemeral=True) return - # Créer l'embed de confirmation - 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=disnake.Color.orange() - ) - - # Ajouter les informations du ticket category = self.config.get_category(self.ticket.category_id) duration = self.ticket.duration_minutes - - embed.add_field(name="📁 Catégorie", value=category.name if category else "N/A", inline=True) - embed.add_field(name="⏱️ Durée", value=f"{duration:.1f} min" if duration else "N/A", inline=True) - embed.add_field(name="📊 Statut", value=self.ticket.status.value.capitalize(), inline=True) - - embed.set_footer(text="Cliquez sur Confirmer pour fermer ou Annuler pour annuler l'opération") - - # Afficher la vue de confirmation - view = CloseConfirmationView(self.bot, self.storage, self.config, self.ticket) - await interaction.response.send_message(embed=embed, view=view, ephemeral=True) + duration_text = f"{duration:.1f} min" if duration else "N/A" + + # ✅ V2 components (disnake>=2.11): ne pas utiliser embed/content avec components V2 + # (version minimale pour éviter tout problème de parsing) + # Confirmation UI en V2 (boutons réels) - UI courte. + # Objectif: éviter les erreurs Invalid Form Body et garder un contenu minimal. + components = [ + disnake.ui.Container( + disnake.ui.Section( + "🔒 Confirmer la fermeture", + accessory=disnake.ui.Button( + label="✅ Confirmer", + style=disnake.ButtonStyle.green, + custom_id="close_confirm" + ) + ), + disnake.ui.Separator(divider=True), + disnake.ui.Section( + "❌ Annuler", + accessory=disnake.ui.Button( + label="❌ Annuler", + style=disnake.ButtonStyle.red, + custom_id="close_cancel" + ) + ) + ) + ] + + # On ne met PAS de view= ici (sinon Disnake lève TypeError). + # Les boutons V2 de close_confirm/close_cancel seront routés via on_interaction. + await interaction.response.send_message(components=components, flags=disnake.MessageFlags(is_components_v2=True), ephemeral=True) async def _transcript_callback(self, interaction: disnake.Interaction): """Génère le transcript""" @@ -818,14 +895,11 @@ class TicketManagementView(disnake.ui.View): self.ticket.status = TicketStatus.CLAIMED self.storage.save_ticket(self.ticket) - # Update view (change label) - for item in self.children: - if item.custom_id == "ticket_claim": - item.label = "Transférer" - break - - # Force UI update - await interaction.response.edit_message(view=self) + # Force UI update V2 + try: + await interaction.response.edit_message(components=get_ticket_management_container(self.ticket, self.category, self.config)) + except disnake.NotFound: + pass # Message might have been deleted, ignore if old_claimer: embed = disnake.Embed( @@ -863,24 +937,40 @@ class TicketManagementView(disnake.ui.View): overwrites=overwrites ) - # Update view (remove reopen button, add claim if enabled) - for item in self.children: - if item.custom_id == "ticket_reopen": - self.remove_item(item) - break - - if self.config.claim_enabled and self.category and self.category.allow_claims: - claim_btn = disnake.ui.Button( - style=disnake.ButtonStyle.primary, - label="Réclamer", - custom_id="ticket_claim" - ) - claim_btn.callback = self._claim_callback - self.add_item(claim_btn) + # Update UI V2 + try: + await interaction.response.edit_message(components=get_ticket_management_container(self.ticket, self.category, self.config)) + except disnake.NotFound: + pass embed = TicketEmbedFormatter.ticket_reopened(self.ticket, interaction.user) await interaction.channel.send(embed=embed) - await interaction.response.send_message("Ticket rouvert!", ephemeral=True) + # We might have edited the message, so we just send follow up if we can't edit + # if not interaction.response.is_done(): + # await interaction.response.send_message("Ticket rouvert!", ephemeral=True) + + async def _unclaim_callback(self, interaction: disnake.Interaction): + """Abandonne le ticket""" + if not self._is_staff(interaction): + await interaction.response.send_message("Réservé au staff.", ephemeral=True) + return + + if self.ticket.claimed_by != interaction.user.id: + await interaction.response.send_message("Vous n'êtes pas celui qui a réclamé ce ticket.", ephemeral=True) + return + + self.ticket.claimed_by = None + self.ticket.status = TicketStatus.OPEN + self.storage.save_ticket(self.ticket) + + # Update UI V2 + try: + await interaction.response.edit_message(components=get_ticket_management_container(self.ticket, self.category, self.config)) + except disnake.NotFound: + pass + + embed = TicketEmbedFormatter.ticket_unclaimed(self.ticket, interaction.user) + await interaction.channel.send(embed=embed) def _is_staff(self, interaction: disnake.Interaction) -> bool: return is_staff(interaction, self.config, self.category) @@ -924,7 +1014,7 @@ class CloseConfirmationView(disnake.ui.View): self.category = config.get_category(ticket.category_id) @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): + async def confirm_callback(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction): """L'utilisateur confirme la fermeture""" # Vérifier les permissions can_close = ( @@ -936,24 +1026,40 @@ class CloseConfirmationView(disnake.ui.View): await interaction.response.send_message("Vous ne pouvez pas fermer ce ticket.", ephemeral=True) return - # Show the delay view with countdown - 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=disnake.Color.orange() - ) + # Show the delay view with countdown (V2 components, not embeds) + components = [ + disnake.ui.Container( + disnake.ui.Section( + "# ⏳ Fermeture du Ticket", + accessory=disnake.ui.Button( + label="Fermeture...", + style=disnake.ButtonStyle.secondary, + custom_id="close_delay_info" + ) + ), + disnake.ui.Separator(divider=True), + disnake.ui.TextDisplay( + content="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..." + ) + ) + ] view = CloseDelayView(self.bot, self.storage, self.config, self.ticket) - await interaction.response.send_message(embed=embed, view=view, ephemeral=True) + await interaction.response.send_message( + components=components, + flags=disnake.MessageFlags(is_components_v2=True), + view=view, + ephemeral=True + ) # Start the countdown await view.start_countdown(interaction) @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): + async def cancel_callback(self, button: disnake.ui.Button, interaction: disnake.MessageInteraction): """L'utilisateur annule la fermeture""" await interaction.response.defer() await interaction.delete_original_response() @@ -1173,42 +1279,20 @@ class CloseTicketModal(disnake.ui.Modal): """Modal pour fermer un ticket""" def __init__(self, bot, storage, config: GuildTicketConfig, ticket: TicketData): - super().__init__(title="Fermer le Ticket") + self.reason_input = disnake.ui.TextInput( + label="Raison de fermeture", + placeholder="Optionnel...", + style=disnake.TextInputStyle.paragraph, + required=False + ) + super().__init__(title="Fermer le Ticket", components=[self.reason_input]) self.bot = bot self.storage = storage self.config = config self.ticket = ticket self.category = config.get_category(ticket.category_id) - - self.reason = disnake.ui.TextInput( - label="Raison de fermeture", - placeholder="Optionnel...", - style=disnake.TextStyle.paragraph, - required=False, - max_length=300 - ) - self.add_item(self.reason) - - # Survey fields if enabled - if config.survey_enabled and self.category and self.category.survey_enabled: - self.rating = disnake.ui.TextInput( - label="Note (1-5 étoiles)", - placeholder="1 à 5", - required=False, - max_length=1 - ) - self.add_item(self.rating) - - self.feedback = disnake.ui.TextInput( - label="Commentaires", - placeholder="Vos commentaires sur le support...", - style=disnake.TextStyle.paragraph, - required=False, - max_length=500 - ) - self.add_item(self.feedback) - async def on_submit(self, interaction: disnake.Interaction): + async def callback(self, interaction: disnake.ModalInteraction): # Check permissions can_close = ( interaction.user.id == self.ticket.user_id or @@ -1219,35 +1303,33 @@ class CloseTicketModal(disnake.ui.Modal): await interaction.response.send_message("Vous ne pouvez pas fermer ce ticket.", ephemeral=True) return - # Handle survey if enabled - if hasattr(self, 'rating') and self.rating.value: - try: - rating = int(self.rating.value) - if 1 <= rating <= 5: - self.ticket.rating = rating - else: - await interaction.response.send_message("La note doit être entre 1 et 5.", ephemeral=True) - return - except ValueError: - await interaction.response.send_message("Note invalide.", ephemeral=True) - return - - if hasattr(self, 'feedback') and self.feedback.value: - self.ticket.feedback = self.feedback.value - # Send ephemeral response to user first (required for modal interactions) await interaction.response.send_message("✅ Fermeture du ticket en cours...", ephemeral=True) - # Send confirmation message in the ticket channel - 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=disnake.Color.orange() + # Send confirmation message in the ticket channel (V2 components, not embeds) + confirm_components = [ + disnake.ui.Container( + disnake.ui.Section( + "# ⏳ Fermeture du Ticket", + accessory=disnake.ui.Button( + label="Fermeture...", + style=disnake.ButtonStyle.secondary, + custom_id="close_ticket_modal_info" + ) + ), + disnake.ui.Separator(divider=True), + disnake.ui.TextDisplay( + content="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..." + ) + ) + ] + await interaction.channel.send( + components=confirm_components, + flags=disnake.MessageFlags(is_components_v2=True) ) - await interaction.channel.send(embed=confirm_embed) # Add small delay for user to see the confirmation import asyncio @@ -1305,8 +1387,8 @@ class CloseTicketModal(disnake.ui.Modal): log_embed.add_field(name="Catégorie", value=self.category.name if self.category else "N/A", inline=True) log_embed.add_field(name="Durée", value=f"{duration:.1f} min" if duration else "N/A", inline=True) - if self.reason.value: - log_embed.add_field(name="Raison de fermeture", value=self.reason.value, inline=False) + if self.reason_input.value: + log_embed.add_field(name="Raison de fermeture", value=self.reason_input.value, inline=False) # Send transcript file if transcript_file and os.path.exists(transcript_file): @@ -1826,13 +1908,7 @@ class AdvancedSetupView(disnake.ui.View): class MaxTicketsModal(disnake.ui.Modal): """Modal pour définir la limite de tickets par utilisateur""" - def __init__(self, bot, storage, config: GuildTicketConfig): - super().__init__(title="Limite de Tickets par Utilisateur") - self.bot = bot - self.storage = storage - self.config = config - self.max_tickets = disnake.ui.TextInput( label="Nombre maximum de tickets ouverts par utilisateur", placeholder="5", @@ -1840,9 +1916,12 @@ class MaxTicketsModal(disnake.ui.Modal): required=True, max_length=2 ) - self.add_item(self.max_tickets) + super().__init__(title="Limite de Tickets", components=[self.max_tickets]) + self.bot = bot + self.storage = storage + self.config = config - async def on_submit(self, interaction: disnake.Interaction): + async def callback(self, interaction: disnake.ModalInteraction): try: max_tickets = int(self.max_tickets.value) if max_tickets < 1 or max_tickets > 50: @@ -1996,141 +2075,105 @@ class ConfigPanelView(disnake.ui.View): class AddCategoryModal(disnake.ui.Modal): """Modal pour ajouter une catégorie""" - def __init__(self, bot, storage, config: GuildTicketConfig): - super().__init__(title="Nouvelle Catégorie") + self.name_input = disnake.ui.TextInput(label="Nom", custom_id="add_cat_name", placeholder="Ex: Support", required=True) + self.desc_input = disnake.ui.TextInput(label="Description", custom_id="add_cat_desc", placeholder="Description...", required=False, style=disnake.TextInputStyle.paragraph) + self.emoji_input = disnake.ui.TextInput(label="Emoji", custom_id="add_cat_emoji", placeholder="🎫", required=False, max_length=5) + super().__init__(title="Ajouter une Catégorie", components=[self.name_input, self.desc_input, self.emoji_input]) self.bot = bot self.storage = storage self.config = config - - self.name = disnake.ui.TextInput(label="Nom", placeholder="Ex: Support", required=True) - self.add_item(self.name) - - self.description = disnake.ui.TextInput( - label="Description", placeholder="Description...", required=False, style=disnake.TextStyle.paragraph - ) - self.add_item(self.description) - - self.emoji = disnake.ui.TextInput(label="Emoji", placeholder="🎫", required=False, max_length=5) - self.add_item(self.emoji) - async def on_submit(self, interaction: disnake.Interaction): + async def callback(self, interaction: disnake.ModalInteraction): import uuid cat_id = str(uuid.uuid4())[:8] + name_val = interaction.text_values.get("add_cat_name", "Nouvelle Catégorie") + desc_val = interaction.text_values.get("add_cat_desc", "") + emoji_val = interaction.text_values.get("add_cat_emoji", "🎫") + category = TicketCategory( id=cat_id, - name=self.name.value, - description=self.description.value or "", - emoji=self.emoji.value or "🎫" + name=name_val, + description=desc_val, + emoji=emoji_val ) self.config.categories.append(category) self.storage.save_config(interaction.guild_id, self.config) - await interaction.response.send_message(f"Catégorie **{self.name.value}** créée!", ephemeral=True) + await interaction.response.send_message(f"Catégorie **{name_val}** créée!", ephemeral=True) class EditCategoryModal(disnake.ui.Modal): """Modal pour modifier une catégorie existante""" def __init__(self, bot, storage, config, category: TicketCategory, parent_view): - super().__init__(title=f"Modifier: {category.name}") + self.name_input = disnake.ui.TextInput(label="Nom", custom_id="edit_cat_name", value=category.name, placeholder="Ex: Support", required=True) + self.desc_input = disnake.ui.TextInput(label="Description", custom_id="edit_cat_desc", value=category.description, placeholder="Description...", required=False, style=disnake.TextInputStyle.paragraph) + self.emoji_input = disnake.ui.TextInput(label="Emoji", custom_id="edit_cat_emoji", value=category.emoji, placeholder="🎫", required=False, max_length=5) + super().__init__(title=f"Modifier: {category.name}", components=[self.name_input, self.desc_input, self.emoji_input]) self.bot = bot self.storage = storage self.config = config self.category = category self.parent_view = parent_view - - self.name = disnake.ui.TextInput( - label="Nom", - default=category.name, - placeholder="Ex: Support", - required=True - ) - self.add_item(self.name) - - self.description = disnake.ui.TextInput( - label="Description", - default=category.description, - placeholder="Description...", - required=False, - style=disnake.TextStyle.paragraph - ) - self.add_item(self.description) - - self.emoji = disnake.ui.TextInput( - label="Emoji", - default=category.emoji, - placeholder="🎫", - required=False, - max_length=5 - ) - self.add_item(self.emoji) - - async def on_submit(self, interaction: disnake.Interaction): - # Update category object - self.category.name = self.name.value - self.category.description = self.description.value or "" - self.category.emoji = self.emoji.value or "🎫" - - # Save config - self.storage.save_config(interaction.guild_id, self.config) - - # Refresh the parent view's message - await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) - await interaction.followup.send(f"✅ Catégorie **{self.category.name}** mise à jour !", ephemeral=True) + async def callback(self, interaction: disnake.ModalInteraction): + try: + self.category.name = interaction.text_values.get("edit_cat_name", self.category.name) + self.category.description = interaction.text_values.get("edit_cat_desc", "") + self.category.emoji = interaction.text_values.get("edit_cat_emoji", "🎫") + self.storage.save_config(interaction.guild_id, self.config) + + container = disnake.ui.Container( + disnake.ui.TextDisplay(content=f"✅ Catégorie **{self.category.name}** mise à jour !"), + disnake.ui.Section("Navigation", accessory=disnake.ui.Button(label="Retour", style=disnake.ButtonStyle.secondary, custom_id=f"ticket_config_cat_manage_{self.category.id}")) + ) + + await interaction.response.edit_message(components=[container]) + except Exception as e: + import traceback + kuby_logger.error(f"[EditCategoryModal] Error: {e}\n{traceback.format_exc()}") + if not interaction.response.is_done(): + await interaction.response.send_message(f"❌ Erreur critique : {e}", ephemeral=True) class CategoryManagerView(disnake.ui.View): - """Vue pour lister et gérer les catégories""" - + """Vue pour lister et gérer les catégories (Modernisée V2)""" def __init__(self, bot, storage, config: GuildTicketConfig): super().__init__(timeout=None) self.bot = bot self.storage = storage self.config = config - self._add_category_select() + + def get_components_v2(self): + sections = [ + disnake.ui.Section( + "# 📂 Gestion des Catégories", + accessory=disnake.ui.Button(label="➕ Ajouter", style=disnake.ButtonStyle.green, custom_id="ticket_config_cat_add") + ), + disnake.ui.Separator(divider=True) + ] - def _add_category_select(self): - self.clear_items() + if not self.config.categories: + sections.append(disnake.ui.TextDisplay(content="Aucune catégorie configurée.")) + else: + for cat in self.config.categories: + sections.append( + disnake.ui.Section( + f"{cat.emoji} **{cat.name}**\n*{cat.description or 'Pas de description'}*", + accessory=disnake.ui.Button(label="Gérer", style=disnake.ButtonStyle.secondary, custom_id=f"ticket_config_cat_manage_{cat.id}") + ) + ) - if self.config.categories: - options = [] - for cat in self.config.categories[:25]: # Limite Select menu - options.append(disnake.SelectOption( - label=cat.name, - value=cat.id, - description=cat.description[:100] if cat.description else "Aucune description", - emoji=cat.emoji - )) - - select = disnake.ui.Select( - placeholder="Sélectionnez une catégorie à gérer...", - options=options, - custom_id="manage_cat_select" - ) - select.callback = self._category_select_callback - self.add_item(select) - - # Bouton Ajouter - add_btn = disnake.ui.Button( - style=disnake.ButtonStyle.success, - label="➕ Ajouter une catégorie", - custom_id="manage_cat_add" - ) - add_btn.callback = self._add_cat_callback - self.add_item(add_btn) + sections.append(disnake.ui.Separator(divider=True)) + sections.append(disnake.ui.Section( + "Retour au menu principal", + accessory=disnake.ui.Button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_main") + )) - # Bouton Retour - back_btn = disnake.ui.Button( - style=disnake.ButtonStyle.secondary, - label="⬅️ Retour", - custom_id="manage_cat_back" - ) - back_btn.callback = self._back_callback - self.add_item(back_btn) + return [disnake.ui.Container(*sections)] def _get_embed(self): embed = disnake.Embed( @@ -2171,7 +2214,7 @@ class CategoryManagerView(disnake.ui.View): super().__init__(bot, storage, config) self.parent_view = parent_view - async def on_submit(self, interaction: disnake.Interaction): + async def callback(self, interaction: disnake.ModalInteraction): import uuid cat_id = str(uuid.uuid4())[:8] category = TicketCategory( @@ -2230,12 +2273,12 @@ class CategoryActionView(disnake.ui.View): return embed @disnake.ui.button(label="📝 Modifier", style=disnake.ButtonStyle.primary) - async def edit_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def edit_callback(self, interaction: disnake.MessageInteraction): modal = EditCategoryModal(self.bot, self.storage, self.config, self.category, self) await interaction.response.send_modal(modal) @disnake.ui.button(label="🛡️ Permissions", style=disnake.ButtonStyle.success) - async def permissions_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def permissions_callback(self, interaction: disnake.MessageInteraction): view = CategoryPermissionsView(self.bot, self.storage, self.config, self.category, self) embed = disnake.Embed( title=f"Permissions: {self.category.name}", @@ -2247,7 +2290,7 @@ class CategoryActionView(disnake.ui.View): await interaction.response.edit_message(embed=embed, view=view) @disnake.ui.button(label="📁 Catégorie Discord", style=disnake.ButtonStyle.secondary) - async def discord_category_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def discord_category_callback(self, interaction: disnake.MessageInteraction): view = CategoryDiscordCategoryView(self.bot, self.storage, self.config, self.category, self) embed = disnake.Embed( title=f"Catégorie Discord: {self.category.name}", @@ -2258,7 +2301,7 @@ class CategoryActionView(disnake.ui.View): await interaction.response.edit_message(embed=embed, view=view) @disnake.ui.button(label="🗑️ Supprimer", style=disnake.ButtonStyle.danger) - async def delete_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def delete_callback(self, interaction: disnake.MessageInteraction): view = DeleteCategoryConfirmationView(self.bot, self.storage, self.config, self.category, self.parent_view) embed = disnake.Embed( title="⚠️ Confirmer la suppression", @@ -2269,7 +2312,7 @@ class CategoryActionView(disnake.ui.View): await interaction.response.edit_message(embed=embed, view=view) @disnake.ui.button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary) - async def back_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def back_callback(self, interaction: disnake.MessageInteraction): self.parent_view._add_category_select() await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) @@ -2285,8 +2328,8 @@ class CategoryPermissionsView(disnake.ui.View): self.parent_view = parent_view @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] + async def role_select_callback(self, interaction: disnake.MessageInteraction): + role_ids = [str(role.id) for role in interaction.values] self.category.staff_roles = role_ids self.storage.save_config(interaction.guild_id, self.config) @@ -2294,7 +2337,7 @@ class CategoryPermissionsView(disnake.ui.View): await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) @disnake.ui.button(label="⬅️ Annuler", style=disnake.ButtonStyle.secondary) - async def back_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def back_callback(self, interaction: disnake.MessageInteraction): await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) class CategoryDiscordCategoryView(disnake.ui.View): @@ -2309,19 +2352,20 @@ class CategoryDiscordCategoryView(disnake.ui.View): self.parent_view = parent_view @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 + async def select_callback(self, interaction: disnake.MessageInteraction): + val = interaction.values[0] + self.category.discord_category_id = val.id if hasattr(val, 'id') else int(val) self.storage.save_config(interaction.guild_id, self.config) await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) @disnake.ui.button(label="❌ Réinitialiser", style=disnake.ButtonStyle.danger) - async def reset_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def reset_callback(self, interaction: disnake.MessageInteraction): 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) @disnake.ui.button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary) - async def back_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def back_callback(self, interaction: disnake.MessageInteraction): await interaction.response.edit_message(embed=self.parent_view._get_embed(), view=self.parent_view) @@ -2335,7 +2379,7 @@ class DeleteCategoryConfirmationView(disnake.ui.View): self.parent_view = parent_view @disnake.ui.button(label="✅ Confirmer", style=disnake.ButtonStyle.danger) - async def confirm_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def confirm_callback(self, interaction: disnake.MessageInteraction): if self.category in self.config.categories: self.config.categories.remove(self.category) self.storage.save_config(interaction.guild_id, self.config) @@ -2348,74 +2392,104 @@ class DeleteCategoryConfirmationView(disnake.ui.View): await interaction.followup.send(f"✅ Catégorie **{self.category.name}** supprimée.", ephemeral=True) @disnake.ui.button(label="❌ Annuler", style=disnake.ButtonStyle.secondary) - async def cancel_callback(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def cancel_callback(self, interaction: disnake.MessageInteraction): 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(disnake.ui.Modal): - """Modal pour définir le canal de logs""" - +class TicketChannelConfigView(disnake.ui.View): + """Vue moderne pour configurer un salon via un sélecteur (V2)""" def __init__(self, bot, storage, config: GuildTicketConfig, channel_type: str = "log"): - super().__init__(title="Définir le Canal de Logs") + super().__init__(timeout=None) self.bot = bot self.storage = storage self.config = config self.channel_type = channel_type - - self.channel = disnake.ui.TextInput( - label="Canal de logs", - placeholder="Mentionnez le canal ou entrez l'ID (ex: #general ou 123456789)", - required=True, - max_length=100 + + # Sélecteur de salon + select = disnake.ui.ChannelSelect( + placeholder="Sélectionnez la catégorie dans la liste...", + channel_types=[disnake.ChannelType.text], + min_values=1, + max_values=1, + custom_id="ticket_config_chan_select" ) - self.add_item(self.channel) - - async def on_submit(self, interaction: disnake.Interaction): - channel_input = self.channel.value.strip() + self.add_item(disnake.ui.ChannelSelect(placeholder="Choisir un salon...", channel_types=c_types, custom_id=f"ticket_config_select_{self.channel_type}")) + + def get_components_v2(self): + title = "📝 Configuration des Logs" if self.channel_type == "log" else "📁 Catégorie de Destination" + desc = "Choisissez la catégorie où les tickets seront créés." if self.channel_type == "log" else "Choisissez la catégorie où les tickets seront créés." - # Try to extract channel ID from mention or ID - channel_id = None + sections = [ + disnake.ui.TextDisplay(content=f"# {title}\n{desc}"), + disnake.ui.Separator(divider=True), + disnake.ui.TextDisplay(content="Utilisez le menu déroulant ci-dessous pour choisir un salon."), + disnake.ui.Separator(divider=True), + disnake.ui.Section("Navigation", accessory=disnake.ui.Button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_main")) + ] - # Check for mention format (#channel) - if channel_input.startswith('<#') and channel_input.endswith('>'): - try: - channel_id = int(channel_input[2:-1]) - except ValueError: - pass - # Check for raw ID - else: - try: - channel_id = int(channel_input.replace('<', '').replace('>', '')) - except ValueError: - pass + c_types = [disnake.ChannelType.text] if self.channel_type == "log" else [disnake.ChannelType.category] - if not channel_id: - await interaction.response.send_message( - "❌ Format invalide. Utilisez une mention (#channel) ou un ID de canal.", - ephemeral=True + # Include the select menu in the components list + return [disnake.ui.Container(*sections), disnake.ui.ChannelSelect(placeholder="Choisir une catégorie...", channel_types=c_types, custom_id=f"ticket_config_select_{self.channel_type}")] + + async def _select_callback(self, inter: disnake.MessageInteraction): + print(f"DEBUG: _select_callback called. Values: {inter.values}") + try: + val = inter.values[0] + channel_id = val.id if hasattr(val, "id") else int(val) + print(f"DEBUG: channel_id determined: {channel_id}") + + if self.channel_type == "log": + self.config.log_channel_id = channel_id + elif self.channel_type.startswith("cat_"): + cat_id = self.channel_type.replace("cat_", "") + category = next((c for c in self.config.categories if c.id == cat_id), None) + if category: + category.discord_category_id = channel_id + + self.storage.save_config(inter.guild_id, self.config) + print(f"DEBUG: Config saved for guild {inter.guild_id}") + + # Success message V2 + container = disnake.ui.Container( + disnake.ui.TextDisplay(content=f"✅ Salon/Catégorie configuré(e) sur <#{channel_id}>"), + disnake.ui.Section("Navigation", accessory=disnake.ui.Button(label="Retour", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_main")) ) - return - - # Verify channel exists - channel = interaction.guild.get_channel(channel_id) - if not channel: - await interaction.response.send_message( - "❌ Ce canal n'existe pas sur ce serveur.", - ephemeral=True + print("DEBUG: Sending success message...") + await inter.response.edit_message(components=[container]) + print("DEBUG: Success message sent.") + except Exception as e: + print(f"DEBUG ERROR in _select_callback: {e}") + import traceback + traceback.print_exc() + kuby_logger.error(f"❌ Erreur dans _select_callback: {e}", exc_info=True) + try: + await inter.response.send_message(f"❌ Erreur lors de la configuration du salon : {e}", ephemeral=True) + except: + pass + +class SetChannelModal(disnake.ui.Modal): + + async def callback(self, interaction: disnake.ModalInteraction): + try: + channel_id = int(self.channel_input.value.strip()) + channel = interaction.guild.get_channel(channel_id) + if not channel: + await interaction.response.send_message("❌ Canal introuvable.", ephemeral=True) + return + + self.config.log_channel_id = channel_id + self.storage.save_config(interaction.guild_id, self.config) + + embed = disnake.Embed( + title="✅ Canal de Logs Mis à Jour", + description=f"Le canal de logs a été défini sur {channel.mention}.", + color=disnake.Color.green() ) - return - - # Update config - self.config.log_channel_id = channel_id - self.storage.save_config(interaction.guild_id, self.config) - - embed = disnake.Embed( - title="✅ Canal de Logs Mis à Jour", - description=f"Le canal de logs a été défini sur {channel.mention}.", - color=disnake.Color.green() - ) - await interaction.response.send_message(embed=embed, ephemeral=True) + await interaction.response.send_message(embed=embed, ephemeral=True) + except ValueError: + await interaction.response.send_message("❌ ID invalide.", ephemeral=True) class StaffRolesModal(disnake.ui.Modal): @@ -2436,7 +2510,7 @@ class StaffRolesModal(disnake.ui.Modal): ) self.add_item(self.roles) - async def on_submit(self, interaction: disnake.Interaction): + async def callback(self, interaction: disnake.ModalInteraction): try: role_input = self.roles.value.strip() role_ids = [] @@ -2524,7 +2598,8 @@ class PermissionConfigView(disnake.ui.View): placeholder=f"Sélectionnez les rôles {self.config_type}", min_values=0, max_values=25, - row=0 + row=0, + custom_id=f"ticket_config_perms_role_{self.config_type}" ) select.callback = self._role_callback self.add_item(select) @@ -2534,7 +2609,8 @@ class PermissionConfigView(disnake.ui.View): placeholder=f"Sélectionnez les utilisateurs {self.config_type}", min_values=0, max_values=25, - row=1 + row=1, + custom_id=f"ticket_config_perms_user_{self.config_type}" ) select.callback = self._user_callback self.add_item(select) @@ -2600,42 +2676,22 @@ class PermissionConfigView(disnake.ui.View): self._add_role_select() self._add_user_select() self._add_remove_select() - - # Rebuild Embed - if self.config_type == "staff": - title = "Permissions Staff" - description = "Configurez qui peut gérer les tickets (répondre, fermer, etc.)" - 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 = disnake.Color.orange() - target_roles = self.config.config_roles - target_users = self.config.config_users + await interaction.response.edit_message(components=self.get_components_v2()) - embed = disnake.Embed(title=title, description=description, color=color) + def get_components_v2(self): + title = "🛡️ Permissions Staff" if self.config_type == "staff" else "⚙️ Permissions Config" + desc = "Configurez qui peut gérer les tickets." if self.config_type == "staff" else "Configurez qui peut modifier les paramètres." - # Add fields logic (inline reuse) - role_mentions = [] - for role_id in target_roles: - role = self.guild.get_role(int(role_id)) - if role: - role_mentions.append(role.mention) + sections = [ + disnake.ui.TextDisplay(content=f"# {title}\n{desc}"), + disnake.ui.Separator(divider=True), + disnake.ui.TextDisplay(content="Utilisez les sélecteurs ci-dessous pour modifier les permissions."), + disnake.ui.Separator(divider=True), + disnake.ui.Section("Navigation", accessory=disnake.ui.Button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_main")) + ] - user_mentions = [] - for user_id in target_users: - user = self.guild.get_member(user_id) - if user: - user_mentions.append(user.mention) - else: - user_mentions.append(f"<@{user_id}>") - - 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) - - await interaction.response.edit_message(embed=embed, view=self) + # Combine container with view children (RoleSelect, UserSelect, etc.) + return [disnake.ui.Container(*sections)] + self.children def _add_remove_select(self): # Build options from current config @@ -2683,7 +2739,7 @@ class PermissionConfigView(disnake.ui.View): min_values=1, max_values=min(len(options), 25), row=2, - custom_id="remove_select" + custom_id=f"ticket_config_perms_remove_{self.config_type}" ) select.callback = self._remove_callback self.add_item(select) @@ -2734,6 +2790,278 @@ class TicketCommands(commands.Cog): async def cog_load(self): await self._register_all_views() + @commands.Cog.listener() + async def on_interaction(self, inter: disnake.Interaction): + """Gère les interactions des composants V2 (Container/Section)""" + # Logs ultra détaillés pour tracer "interaction failed" / mauvais payload + try: + inter_data = getattr(inter, "data", None) + inter_custom_id = getattr(inter_data, "custom_id", None) if inter_data else None + inter_values = getattr(inter_data, "values", None) if inter_data else None + except Exception: + inter_custom_id = None + inter_values = None + + kuby_logger.info( + f"[TicketSystem] on_interaction: type={getattr(inter, 'type', None)} " + f"custom_id={inter_custom_id} values={inter_values} " + f"guild_id={getattr(getattr(inter,'guild',None),'id',None)} channel_id={getattr(getattr(inter,'channel',None),'id',None)}" + ) + + if inter.type != disnake.InteractionType.component: + return + + if not inter.guild: + return + + custom_id = inter.data.custom_id + # Debug: savoir si on rentre dans le handler ticket_* + kuby_logger.info(f"[TicketSystem] Routing check: custom_id={custom_id} startswith 'ticket_'? {bool(custom_id and str(custom_id).startswith('ticket_'))}") + + if not custom_id: + return + + # Accept both ticket_* / cat_* / close_* custom_ids + if not ( + str(custom_id).startswith("ticket_") + or str(custom_id).startswith("cat_") + or str(custom_id) in {"close_confirm", "close_cancel", "delay_cancel"} + ): + return + + if not inter.guild: + return + + custom_id = inter.data.custom_id + + try: + # On récupère la config + config = self.storage.load_config(inter.guild.id) + if not config: + kuby_logger.warning(f"[TicketSystem] No config for guild_id={inter.guild.id}") + return + except Exception as e: + kuby_logger.error(f"[TicketSystem] Failed to load config: {e}", exc_info=True) + if not inter.response.is_done(): + await inter.response.send_message("Erreur interne (config)", ephemeral=True) + return + + try: + + # Routage manuel + if custom_id == "ticket_create": + view = TicketPanelView(self.bot, config, self.storage) + await view._create_callback(inter) + elif custom_id == "ticket_my_tickets": + view = TicketPanelView(self.bot, config, self.storage) + await view._my_tickets_callback(inter) + elif custom_id == "ticket_analytics": + view = TicketPanelView(self.bot, config, self.storage) + await view._analytics_callback(inter) + + # ✅ BUGFIX: Sélection de catégorie en V2 => custom_id = cat_{cat.id}_{snowflake} + elif custom_id.startswith("cat_"): + try: + # format: cat__ + parts = custom_id.split("_") + if len(parts) >= 3: + cat_id = "_".join(parts[1:-1]) + else: + cat_id = parts[1] if len(parts) > 1 else None + + category = next((c for c in config.categories if c.id == cat_id), None) + if not category: + await inter.response.send_message("Catégorie non trouvée.", ephemeral=True) + return + + # ouvrir directement le modal de priorité (même logique que CategorySelectionView) + modal = TicketPriorityModal(self.bot, config, category) + await inter.response.send_modal(modal) + except Exception as e: + kuby_logger.error(f"[TicketSystem] cat_ routing error: {e}", exc_info=True) + if not inter.response.is_done(): + await inter.response.send_message(f"Erreur interne: {e}", ephemeral=True) + + elif custom_id == "ticket_claim": + ticket = self.storage.load_ticket(inter.channel.id) + if ticket: + view = TicketManagementView(self.bot, self.storage, config, ticket) + await view._claim_callback(inter) + elif custom_id == "ticket_unclaim": + ticket = self.storage.load_ticket(inter.channel.id) + if ticket: + view = TicketManagementView(self.bot, self.storage, config, ticket) + await view._unclaim_callback(inter) + elif custom_id == "ticket_close": + ticket = self.storage.load_ticket(inter.channel.id) + if ticket: + view = TicketManagementView(self.bot, self.storage, config, ticket) + await view._close_callback(inter) + elif custom_id == "ticket_reopen": + ticket = self.storage.load_ticket(inter.channel.id) + if ticket: + view = TicketManagementView(self.bot, self.storage, config, ticket) + await view._reopen_callback(inter) + elif custom_id == "ticket_transcript": + ticket = self.storage.load_ticket(inter.channel.id) + if ticket: + view = TicketManagementView(self.bot, self.storage, config, ticket) + await view._transcript_callback(inter) + + # === Fermeture via composants V2 (sans view=, donc custom_id) === + elif custom_id == "close_cancel": + await inter.response.defer() + # Retire la réponse éphémère du user (si c'est bien une réponse d'interaction) + try: + await inter.delete_original_response() + except Exception: + pass + + elif custom_id == "close_confirm": + try: + kuby_logger.info(f"[TicketSystem] close_confirm clicked channel={getattr(inter.channel,'id',None)} user={inter.user.id if inter.user else None}") + + # Permission check identique à CloseConfirmationView.confirm_callback + ticket = self.storage.load_ticket(inter.channel.id) if inter.channel else None + if not ticket: + await inter.response.send_message("Ticket non trouvé.", ephemeral=True) + return + + can_close = ( + inter.user.id == ticket.user_id or + is_staff(inter, config, config.get_category(ticket.category_id)) + ) + if not can_close: + await inter.response.send_message("Vous ne pouvez pas fermer ce ticket.", ephemeral=True) + return + + await inter.response.defer() + + close_view = CloseDelayView(self.bot, self.storage, config, ticket) + + # Compte à rebours dans le CHANNEL (pas sur l'ephemeral), puis fermeture. + steps = [ + ("⏳ Préparation...", 1), + ("📄 Génération du transcript...", 1), + ("📝 Sauvegarde des données...", 1), + ("🗑️ Suppression du canal...", 1) + ] + + channel = inter.channel + if not channel: + await inter.followup.send("Canal introuvable.", ephemeral=True) + return + + countdown_msg = await channel.send( + embed=disnake.Embed( + title="🔒 Fermeture du Ticket", + description="**⏳ Préparation...**\n\nMerci pour votre confiance!", + color=disnake.Color.orange() + ) + ) + + for step_text, delay in steps: + embed = disnake.Embed( + title="🔒 Fermeture du Ticket", + description=f"**{step_text}**\n\nMerci pour votre confiance!", + color=disnake.Color.orange() + ) + embed.set_footer(text=f"Ticket #{ticket.channel_id}") + await countdown_msg.edit(embed=embed) + await asyncio.sleep(delay) + + await close_view._close_ticket(inter) + + try: + await countdown_msg.delete() + except Exception: + pass + + except Exception as e: + kuby_logger.error(f"[TicketSystem] close_confirm failed: {e}", exc_info=True) + if not inter.response.is_done(): + await inter.response.send_message("❌ Échec de la fermeture (voir logs).", ephemeral=True) + + # Configuration + elif custom_id == "ticket_config_main": + await self.ticket_config(inter) + elif custom_id == "ticket_config_toggle": + config.enabled = not config.enabled + self.storage.save_config(inter.guild.id, config) + await self.ticket_config(inter) + elif custom_id == "ticket_config_manage_cat": + view = CategoryManagerView(self.bot, self.storage, config) + await inter.response.edit_message(components=view.get_components_v2()) + elif custom_id == "ticket_config_cat_add": + modal = AddCategoryModal(self.bot, self.storage, config) + await inter.response.send_modal(modal) + elif custom_id.startswith("ticket_config_cat_manage_"): + cat_id = custom_id.replace("ticket_config_cat_manage_", "") + category = next((c for c in config.categories if c.id == cat_id), None) + if category: + # Show modern category actions + actions_container = disnake.ui.Container( + disnake.ui.TextDisplay(content=f"# 🛠️ Gérer: {category.name}"), + disnake.ui.Separator(divider=True), + disnake.ui.Section("Modifier les informations de base (Nom, Desc, Emoji)", accessory=disnake.ui.Button(label="✏️ Modifier", style=disnake.ButtonStyle.blurple, custom_id=f"ticket_config_cat_edit_{cat_id}")), + disnake.ui.Section("Configurer les permissions staff spécifiques", accessory=disnake.ui.Button(label="🛡️ Permissions", style=disnake.ButtonStyle.secondary, custom_id=f"ticket_config_cat_perms_{cat_id}")), + disnake.ui.Section("Définir la catégorie Discord de destination", accessory=disnake.ui.Button(label="📁 Catégorie", style=disnake.ButtonStyle.secondary, custom_id=f"ticket_config_cat_chan_{cat_id}")), + disnake.ui.Separator(divider=True), + disnake.ui.Section("Supprimer définitivement cette catégorie", accessory=disnake.ui.Button(label="🗑️ Supprimer", style=disnake.ButtonStyle.danger, custom_id=f"ticket_config_cat_del_{cat_id}")), + disnake.ui.Section("Navigation", accessory=disnake.ui.Button(label="⬅️ Retour", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_manage_cat")) + ) + await inter.response.edit_message(components=[actions_container]) + elif custom_id.startswith("ticket_config_cat_edit_"): + cat_id = custom_id.replace("ticket_config_cat_edit_", "") + category = next((c for c in config.categories if c.id == cat_id), None) + if category: + modal = EditCategoryModal(self.bot, self.storage, config, category, None) + await inter.response.send_modal(modal) + elif custom_id == "ticket_config_log_channel": + view = TicketChannelConfigView(self.bot, self.storage, config, "log") + await inter.response.edit_message(components=view.get_components_v2()) + elif custom_id.startswith("ticket_config_cat_chan_"): + cat_id = custom_id.replace("ticket_config_cat_chan_", "") + view = TicketChannelConfigView(self.bot, self.storage, config, f"cat_{cat_id}") + await inter.response.edit_message(components=view.get_components_v2()) + elif custom_id == "ticket_config_staff_perms": + view = PermissionConfigView(self.bot, self.storage, config, inter.guild, "staff") + await inter.response.edit_message(components=view.get_components_v2()) + elif custom_id.startswith("ticket_config_cat_perms_"): + cat_id = custom_id.replace("ticket_config_cat_perms_", "") + view = PermissionConfigView(self.bot, self.storage, config, inter.guild, f"cat_{cat_id}") + await inter.response.edit_message(components=view.get_components_v2()) + elif custom_id == "ticket_config_perms_config": + view = PermissionConfigView(self.bot, self.storage, config, inter.guild, "config") + await inter.response.edit_message(components=view.get_components_v2()) + elif custom_id.startswith("ticket_config_select_"): + channel_type = custom_id.replace("ticket_config_select_", "") + view = TicketChannelConfigView(self.bot, self.storage, config, channel_type) + await view._select_callback(inter) + elif custom_id.startswith("ticket_config_perms_role_"): + config_type = custom_id.replace("ticket_config_perms_role_", "") + view = PermissionConfigView(self.bot, self.storage, config, inter.guild, config_type) + await view._role_callback(inter) + elif custom_id.startswith("ticket_config_perms_user_"): + config_type = custom_id.replace("ticket_config_perms_user_", "") + view = PermissionConfigView(self.bot, self.storage, config, inter.guild, config_type) + await view._user_callback(inter) + elif custom_id.startswith("ticket_config_perms_remove_"): + config_type = custom_id.replace("ticket_config_perms_remove_", "") + view = PermissionConfigView(self.bot, self.storage, config, inter.guild, config_type) + await view._remove_callback(inter) + + except Exception as e: + kuby_logger.error(f"❌ Erreur critique dans on_interaction: {e}", exc_info=True) + try: + if not inter.response.is_done(): + await inter.response.send_message(f"❌ Une erreur interne est survenue : {e}", ephemeral=True) + else: + await inter.edit_original_response(content=f"❌ Une erreur interne est survenue : {e}") + except: + pass + + async def _register_all_views(self): """Enregistre les vues pour tous les serveurs""" try: @@ -2766,20 +3094,8 @@ class TicketCommands(commands.Cog): config = self.storage.load_config(inter.guild.id) - embed = disnake.Embed( - title="Système de Tickets", - description="Gérez vos tickets simplement", - 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(inter.guild.id))), inline=True) - view = TicketPanelView(self.bot, config, self.storage) - await inter.edit_original_response(embed=embed, view=view) + await inter.edit_original_response(components=view.get_components_v2()) @commands.slash_command(name="ticketpanel", description="Envoie le panel de création de tickets dans un canal") @commands.has_permissions(administrator=True) @@ -2795,15 +3111,8 @@ class TicketCommands(commands.Cog): target = channel or inter.channel - 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=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 target.send(components=view.get_components_v2()) await inter.edit_original_response(content=f"Panel envoyé dans {target.mention}") @@ -2820,43 +3129,41 @@ class TicketCommands(commands.Cog): await inter.edit_original_response(content="❌ Cette commande est réservée aux administrateurs ou aux rôles configurés.") return + try: + # Build config panel with V2 components + config_container = disnake.ui.Container( + disnake.ui.Section( + "# ⚙️ Configuration des Tickets", + accessory=disnake.ui.Button(label="🔄 Activer/Désactiver", style=disnake.ButtonStyle.green if config.enabled else disnake.ButtonStyle.red, custom_id="ticket_config_toggle") + ), + disnake.ui.Separator(divider=True), + disnake.ui.TextDisplay( + content=f"**État actuel :** {'✅ Activé' if config.enabled else '❌ Désactivé'}\n" + f"**Catégories :** {len(config.categories)}\n" + f"**Rôles staff :** {len(config.staff_roles)}" + ), + disnake.ui.Separator(divider=True), + disnake.ui.Section( + "📂 Gestion des catégories", + accessory=disnake.ui.Button(label="Gérer", style=disnake.ButtonStyle.blurple, custom_id="ticket_config_manage_cat") + ), + disnake.ui.Section( + "📝 Logs & Salons", + accessory=disnake.ui.Button(label="Configurer", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_log_channel") + ), + disnake.ui.Section( + "👥 Permissions Staff", + accessory=disnake.ui.Button(label="Rôles", style=disnake.ButtonStyle.secondary, custom_id="ticket_config_staff_perms") + ) + ) + + await inter.edit_original_response(components=[config_container]) + except Exception as e: + kuby_logger.error(f"❌ Erreur dans ticket_config: {e}", exc_info=True) + await inter.edit_original_response(content=f"❌ Une erreur interne est survenue : {e}") - 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" - "• Activer/Désactiver le système\n" - "• Configurer les rôles staff\n" - "• Gérer les catégories\n" - "• Définir le canal de logs\n" - "• Et bien plus...", - color=disnake.Color.blue() - ) - - embed.add_field( - name="État actuel", - value=f"Système: {'✅ Activé' if config.enabled else '❌ Désactivé'}\n" - f"Catégories: {len(config.categories)}\n" - f"Rôles staff: {len(config.staff_roles)}", - inline=True - ) - - embed.add_field( - name="Configuration requise", - value="• **Rôles Staff**: Définissez qui peut gérer les tickets\n" - "• **Catégories**: Créez des catégories pour organiser les tickets\n" - "• **Canal de Logs**: Où envoyer les notifications", - inline=True - ) - - embed.set_footer(text="Utilisez les boutons ci-dessous pour configurer le système") - - view = ConfigPanelView(self.bot, self.storage, config) - await inter.edit_original_response(embed=embed, view=view) - - -async def setup(bot): +def setup(bot): """Setup function""" - await bot.add_cog(TicketCommands(bot)) + bot.add_cog(TicketCommands(bot)) diff --git a/commandes/ticket/analytics.py b/commandes/ticket/analytics.py index 2737381..e09409a 100644 --- a/commandes/ticket/analytics.py +++ b/commandes/ticket/analytics.py @@ -490,6 +490,6 @@ class TicketAnalytics(commands.Cog): await interaction.followup.send(embed=feedback_embed, ephemeral=True) -async def setup(bot): +def setup(bot): """Setup function for the analytics cog""" - await bot.add_cog(TicketAnalytics(bot)) + bot.add_cog(TicketAnalytics(bot)) diff --git a/commandes/ticket/feedback_view.py b/commandes/ticket/feedback_view.py index fa7ce7d..cedc0ac 100644 --- a/commandes/ticket/feedback_view.py +++ b/commandes/ticket/feedback_view.py @@ -15,11 +15,11 @@ class FeedbackView(disnake.ui.View): self.add_item(RatingSelect()) @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): + async def comment_button(self, button: disnake.ui.Button, interaction: disnake.Interaction): await interaction.response.send_modal(FeedbackModal(self)) @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): + async def submit_button(self, button: disnake.ui.Button, interaction: disnake.Interaction): if self.rating == 0: await interaction.response.send_message("❌ Veuillez sélectionner une note avant d'envoyer.", ephemeral=True) return @@ -128,17 +128,17 @@ class RatingSelect(disnake.ui.Select): class FeedbackModal(disnake.ui.Modal): def __init__(self, view): - super().__init__(title="Votre avis") + super().__init__(components=[], title="Votre avis") self.view = view self.comment = disnake.ui.TextInput( label="Commentaire", - style=disnake.TextStyle.paragraph, + style=disnake.TextInputStyle.paragraph, placeholder="Dites-nous ce que vous avez pensé du support...", required=True, max_length=1000 ) - self.add_item(self.comment) + self.append_component(self.comment) - async def on_submit(self, interaction: disnake.Interaction): + async def callback(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_whitelist.py b/commandes/ticket_whitelist.py index 7f9589f..ac106e1 100644 --- a/commandes/ticket_whitelist.py +++ b/commandes/ticket_whitelist.py @@ -195,7 +195,7 @@ class ContinueFormView(disnake.ui.View): self.step2_part2_data = step2_part2_data @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): + async def continue_form(self, button: disnake.ui.Button, interaction: disnake.Interaction): """Passe à l'étape suivante du formulaire""" if self.current_step == 1: await interaction.response.send_modal(TicketStep2Part1(self.step1_data)) @@ -414,7 +414,7 @@ class TicketManagementView(disnake.ui.View): return None @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): + async def close_ticket(self, button: disnake.ui.Button, interaction: disnake.Interaction): """Ferme le ticket en désactivant l'envoi de messages pour le membre""" await interaction.response.defer(ephemeral=True) @@ -445,7 +445,7 @@ class TicketManagementView(disnake.ui.View): await interaction.followup.send(f"❌ Erreur lors de la fermeture : {e}", ephemeral=True) @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): + async def claim_ticket(self, button: disnake.ui.Button, interaction: disnake.Interaction): """Un staff prend en charge le ticket""" await interaction.response.defer(ephemeral=False) @@ -490,7 +490,7 @@ class ClosedTicketView(disnake.ui.View): return None @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): + async def reopen_ticket(self, button: disnake.ui.Button, interaction: disnake.Interaction): """Réouvre le ticket pour le membre""" await interaction.response.defer(ephemeral=True) @@ -526,15 +526,15 @@ class ReopenStatusView(disnake.ui.View): self.owner = owner @disnake.ui.button(label="Écrit (Ouvert)", style=disnake.ButtonStyle.secondary) - async def set_written(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def set_written(self, button: disnake.ui.Button, interaction: disnake.Interaction): await self.apply_status(interaction, "ouvert") @disnake.ui.button(label="Oral (À faire)", style=disnake.ButtonStyle.primary) - async def set_oral(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def set_oral(self, button: disnake.ui.Button, interaction: disnake.Interaction): await self.apply_status(interaction, "oral-à-faire") @disnake.ui.button(label="Accepté", style=disnake.ButtonStyle.success) - async def set_accepted(self, interaction: disnake.Interaction, button: disnake.ui.Button): + async def set_accepted(self, button: disnake.ui.Button, interaction: disnake.Interaction): await self.apply_status(interaction, "accepté") async def apply_status(self, interaction: disnake.Interaction, prefix: str): @@ -585,7 +585,7 @@ class ReopenStatusView(disnake.ui.View): await interaction.followup.send(f"❌ Erreur système : {e}", ephemeral=True) @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): + async def delete_ticket(self, button: disnake.ui.Button, interaction: disnake.Interaction): """Supprime le salon du ticket""" await interaction.response.defer(ephemeral=True) @@ -611,7 +611,7 @@ class TicketView(disnake.ui.View): super().__init__(timeout=None) @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): + async def open_ticket(self, button: disnake.ui.Button, interaction: disnake.Interaction): """Affiche le premier formulaire""" await interaction.response.send_modal(TicketStep1()) @@ -799,8 +799,8 @@ class Ticket(commands.Cog): kuby_logger.error(f"Erreur lors de la validation du ticket: {e}", exc_info=True) await message.channel.send(f"❌ Une erreur système est survenue : {e}") -async def setup(bot): - await bot.add_cog(Ticket(bot)) +def setup(bot): + bot.add_cog(Ticket(bot)) bot.add_view(TicketView()) bot.add_view(TicketManagementView()) bot.add_view(ClosedTicketView()) \ No newline at end of file diff --git a/commandes/website_sync.py b/commandes/website_sync.py index bb189dd..1f2c831 100644 --- a/commandes/website_sync.py +++ b/commandes/website_sync.py @@ -455,6 +455,6 @@ class WebsiteSync(commands.Cog, name="Website Sync"): # ─── Setup ─────────────────────────────────────────────────────────────────── -async def setup(bot: commands.Bot): - await bot.add_cog(WebsiteSync(bot)) +def setup(bot): + bot.add_cog(WebsiteSync(bot)) logger.info("Cog WebsiteSync chargé.") diff --git a/commandes/welcome.py b/commandes/welcome.py index 5f8172a..f3a4e70 100644 --- a/commandes/welcome.py +++ b/commandes/welcome.py @@ -243,6 +243,6 @@ class Welcome(commands.Cog): bg_used = "background_template.png" if not banner_path else (banner_path.split('/')[-1]) await interaction.response.send_message(f"✅ Configuration de bienvenida enregistrée !\nFond utilisé: {bg_used}", ephemeral=True) -async def setup(bot): - await bot.add_cog(Welcome(bot)) +def setup(bot): + bot.add_cog(Welcome(bot)) diff --git a/commandes/whitelist.py b/commandes/whitelist.py index 4704660..5479a76 100755 --- a/commandes/whitelist.py +++ b/commandes/whitelist.py @@ -95,5 +95,5 @@ class Whitelist(commands.Cog): return self._get_guild_whitelist(guild_id).copy() -async def setup(bot): - await bot.add_cog(Whitelist(bot)) \ No newline at end of file +def setup(bot): + bot.add_cog(Whitelist(bot)) \ No newline at end of file diff --git a/commandes/whitelist_monitor.py b/commandes/whitelist_monitor.py index 7390d02..fc00150 100755 --- a/commandes/whitelist_monitor.py +++ b/commandes/whitelist_monitor.py @@ -293,5 +293,5 @@ class WhitelistRemovalView(View): ephemeral=True ) -async def setup(bot): - await bot.add_cog(WhitelistMonitor(bot)) +def setup(bot): + bot.add_cog(WhitelistMonitor(bot)) diff --git a/data/json-export-history.json b/data/json-export-history.json index d5d9c2e..2b8f1a7 100644 --- a/data/json-export-history.json +++ b/data/json-export-history.json @@ -1,6 +1,6 @@ [ { - "date": "2026-05-01T14:42:53.534230+00:00", + "date": "2026-05-09T16:29:01.039153+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -9,7 +9,7 @@ "success": true }, { - "date": "2026-05-01T14:34:33.777534+00:00", + "date": "2026-05-09T16:24:58.475866+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -18,7 +18,7 @@ "success": true }, { - "date": "2026-05-01T14:31:01.918316+00:00", + "date": "2026-05-09T16:21:25.813131+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -27,7 +27,7 @@ "success": true }, { - "date": "2026-05-01T14:11:08.947818+00:00", + "date": "2026-05-09T16:18:53.874797+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -36,7 +36,7 @@ "success": true }, { - "date": "2026-05-01T13:48:18.180388+00:00", + "date": "2026-05-09T16:15:46.234991+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -45,7 +45,7 @@ "success": true }, { - "date": "2026-05-01T13:36:45.397039+00:00", + "date": "2026-05-09T16:11:35.632086+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -54,7 +54,7 @@ "success": true }, { - "date": "2026-05-01T13:20:32.058899+00:00", + "date": "2026-05-09T16:07:40.809724+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -63,7 +63,7 @@ "success": true }, { - "date": "2026-04-23T14:35:15.364216+00:00", + "date": "2026-05-09T16:05:50.275892+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -72,7 +72,7 @@ "success": true }, { - "date": "2026-04-23T13:35:15.351819+00:00", + "date": "2026-05-09T16:03:53.916964+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -81,7 +81,7 @@ "success": true }, { - "date": "2026-04-23T12:35:15.324906+00:00", + "date": "2026-05-09T16:02:51.029940+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -90,7 +90,7 @@ "success": true }, { - "date": "2026-04-23T11:35:15.210303+00:00", + "date": "2026-05-09T15:51:13.568534+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -99,7 +99,7 @@ "success": true }, { - "date": "2026-04-23T10:35:15.338465+00:00", + "date": "2026-05-09T15:38:58.554617+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -108,7 +108,7 @@ "success": true }, { - "date": "2026-04-23T10:28:55.984163+00:00", + "date": "2026-05-09T15:30:32.050849+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -117,7 +117,7 @@ "success": true }, { - "date": "2026-04-02T17:03:30.870748+00:00", + "date": "2026-05-09T15:28:00.677254+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -126,7 +126,7 @@ "success": true }, { - "date": "2026-04-01T14:00:13.276907+00:00", + "date": "2026-05-09T15:27:08.221370+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -135,7 +135,7 @@ "success": true }, { - "date": "2026-04-01T13:19:44.954622+00:00", + "date": "2026-05-09T15:25:53.264798+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -144,7 +144,7 @@ "success": true }, { - "date": "2026-04-01T13:17:23.537128+00:00", + "date": "2026-05-09T15:19:10.836829+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -153,7 +153,7 @@ "success": true }, { - "date": "2026-04-01T13:15:14.389369+00:00", + "date": "2026-05-09T15:15:08.978927+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -162,7 +162,7 @@ "success": true }, { - "date": "2026-04-01T13:08:00.124432+00:00", + "date": "2026-05-09T15:12:59.706120+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, @@ -171,7 +171,7 @@ "success": true }, { - "date": "2026-04-01T13:03:42.601666+00:00", + "date": "2026-05-09T14:59:44.423282+00:00", "trigger": "auto", "serversCount": 1, "membersCount": 12, diff --git a/data/tickets/tickets/1459561776428351568.json b/data/tickets/tickets/1459561776428351568.json index fa050fb..abe4893 100644 --- a/data/tickets/tickets/1459561776428351568.json +++ b/data/tickets/tickets/1459561776428351568.json @@ -3,7 +3,7 @@ "guild_id": 1369669999345537145, "user_id": 971446412690722826, "category_id": "3b0b9bc1", - "status": "open", + "status": "closed", "priority": "normal", "created_at": "2026-01-10T15:57:24.973539", "closed_at": null, diff --git a/data/tickets/tickets/1461777953376698398.json b/data/tickets/tickets/1461777953376698398.json index 844eb6b..fd9bcac 100644 --- a/data/tickets/tickets/1461777953376698398.json +++ b/data/tickets/tickets/1461777953376698398.json @@ -3,7 +3,7 @@ "guild_id": 1369669999345537145, "user_id": 971446412690722826, "category_id": "91522e2c", - "status": "open", + "status": "closed", "priority": "normal", "created_at": "2026-01-16T18:43:42.737852", "closed_at": null, diff --git a/kuby.py b/kuby.py index 8bb7762..ff01fee 100755 --- a/kuby.py +++ b/kuby.py @@ -28,6 +28,7 @@ kuby_logger.info("✅ Token Discord chargé avec succès") async def main(): kuby_logger.info("🔄 Démarrage de la connexion Discord...") try: + await bot.do_async_setup() kuby_logger.info("🔗 Connexion établie avec Discord...") await bot.start(TOKEN) except KeyboardInterrupt: